> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyformat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sort invoices from receipts

> One workflow for a mailbox that receives both invoices and receipts: Classify decides which is which, and each kind gets its own Extract.

A shared inbox receives invoices and receipts, and they need different fields. One workflow handles both: Parse reads the file, Classify labels it as an invoice or a receipt, and the edge `branch` sends it to the Extract node built for that kind. The document only pays for the Extract it reaches.

<div className="af-payoff">
  <div className="af-payoff-pane">
    <div className="af-payoff-cap">The file from the inbox</div>
    <div className="af-payoff-line"><b>INVOICE</b></div>

    <div className="af-payoff-line">
      <span className="af-mark">
        {'Invoice #: INV-2026-9001'}
      </span>
    </div>

    <div className="af-payoff-line">
      {'Due date: 2026-03-22'}
    </div>

    <div className="af-payoff-line">
      <span className="af-mark">
        {'TOTAL: $4,594.62'}
      </span>
    </div>
  </div>

  <div className="af-payoff-arrow">→</div>

  <div className="af-payoff-pane">
    <div className="af-payoff-cap">What comes back</div>
    <div className="af-payoff-line"><span className="af-payoff-key">category</span><b>{'Invoice'}</b>{'  '}<span className="af-cite">100%</span></div>
    <div className="af-payoff-line"><span className="af-payoff-key">invoice\_number</span><b>{'INV-2026-9001'}</b>{'  '}<span className="af-cite">98%</span></div>
    <div className="af-payoff-line"><span className="af-payoff-key">total\_amount</span><b>{'4594.62'}</b>{'  '}<span className="af-cite">99%</span></div>
    <div className="af-payoff-line"><span className="af-payoff-key">due\_date</span><b>{'2026-03-22'}</b>{'  '}<span className="af-cite">97%</span></div>

    <div className="af-payoff-line">
      <span className="af-cite">
        {'↳ the receipt Extract never ran, and was never billed'}
      </span>
    </div>
  </div>
</div>

<div className="af-cost">
  <span><b>Nodes</b><strong>{'Parse → Classify → Extract ×2'}</strong></span>
  <span><b>Credits</b><strong>52 per page</strong>{' (Flash Parse 7 + Classify 10 + Extract 35)'}</span>
  <span><b>You get</b>{' one label, and the fields of the kind it matched'}</span>
</div>

<div className="af-flow" role="img" aria-label="Parse feeds Classify, which routes an invoice and a receipt to their own Extract nodes">
  <div className="af-flow-fork">
    <div className="af-fork-in">
      <a className="af-node" href="/guides/nodes/parse">Parse</a>

      <span className="af-link" />

      <a className="af-node af-node-on" href="/guides/nodes/classify">Classify</a>
    </div>

    <div className="af-fork-elbow" />

    <div className="af-fork-out">
      <a className="af-node" href="/guides/nodes/extract">Extract <span>invoice</span></a>
      <a className="af-node" href="/guides/nodes/extract">Extract <span>receipt</span></a>
    </div>
  </div>

  <div className="af-flow-note">The edge that leaves Classify names the category id it carries.</div>
</div>

## The workflow

<CodeGroup>
  ```json API theme={null}
  {
    "name": "Inbox: invoices and receipts",
    "nodes": [
      { "id": "parse_1", "type": "parse", "mode": "flash" },
      {
        "id": "classify_1",
        "type": "classify",
        "categories": [
          { "id": "invoice", "name": "Invoice", "description": "A bill for goods or services with a total due" },
          { "id": "receipt", "name": "Receipt", "description": "Proof of a completed payment" }
        ]
      },
      {
        "id": "extract_inv",
        "type": "extract",
        "extraction_schema": { "fields": [
          { "name": "invoice_number", "description": "Invoice identifier", "data_type": "string" },
          { "name": "total_amount",   "description": "Grand total, including tax", "data_type": "float" },
          { "name": "due_date",       "description": "Date payment is due", "data_type": "date" }
        ] }
      },
      {
        "id": "extract_rec",
        "type": "extract",
        "extraction_schema": { "fields": [
          { "name": "merchant",    "description": "Who was paid", "data_type": "string" },
          { "name": "amount_paid", "description": "Amount paid", "data_type": "float" }
        ] }
      }
    ],
    "edges": [
      { "source": "parse_1",    "target": "classify_1" },
      { "source": "classify_1", "target": "extract_inv", "branch": "invoice" },
      { "source": "classify_1", "target": "extract_rec", "branch": "receipt" }
    ]
  }
  ```

  ```python Python theme={null}
  import os

  from anyformat.sdk import Client
  from anyformat.workflow import Schema
  from anyformat.workflow.nodes import ClassifyCategory

  client = Client(api_key=os.environ["ANYFORMAT_API_KEY"])

  invoice = ClassifyCategory(id="invoice", name="Invoice", description="A bill for goods or services with a total due")
  receipt = ClassifyCategory(id="receipt", name="Receipt", description="Proof of a completed payment")

  workflow = (
      client.workflow("Inbox: invoices and receipts")
      .parse(mode="flash")
      .classify(invoice, receipt)
      .extract([
          Schema.string("invoice_number", "Invoice identifier"),
          Schema.float("total_amount", "Grand total, including tax"),
          Schema.date("due_date", "Date payment is due"),
      ], branch=invoice)
      .extract([
          Schema.string("merchant", "Who was paid"),
          Schema.float("amount_paid", "Amount paid"),
      ], branch=receipt)
      .create()
  )
  ```

  ```typescript TypeScript theme={null}
  import { Anyformat, Schema } from "@anyformat/sdk";

  const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });

  const invoice = { id: "invoice", name: "Invoice", description: "A bill for goods or services with a total due" };
  const receipt = { id: "receipt", name: "Receipt", description: "Proof of a completed payment" };

  const workflow = await af
    .workflow("Inbox: invoices and receipts")
    .parse({ mode: "flash" })
    .classify([invoice, receipt])
    .extract([
      Schema.string("invoice_number", "Invoice identifier"),
      Schema.float("total_amount", "Grand total, including tax"),
      Schema.date("due_date", "Date payment is due"),
    ], { branch: invoice })
    .extract([
      Schema.string("merchant", "Who was paid"),
      Schema.float("amount_paid", "Amount paid"),
    ], { branch: receipt })
    .create();
  ```
</CodeGroup>

The edge that leaves Classify carries `branch`, the category **id**. The API rejects an edge out of Classify without one.

## Run it and read the result

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.anyformat.ai/v3/workflows/$WORKFLOW_ID/upload/run/ \
    -H "Authorization: Bearer $ANYFORMAT_API_KEY" \
    -F files=@invoice.pdf
  # → 202 { "run_id": "…", "status": "queued" }

  curl https://api.anyformat.ai/v3/runs/$RUN_ID/ \
    -H "Authorization: Bearer $ANYFORMAT_API_KEY"
  ```

  ```python Python theme={null}
  result = workflow.run("invoice.pdf").wait()

  print(result.raw["classifications"][0]["category"])   # "Invoice"
  print(result.field("total_amount").value)             # "4594.62"
  ```

  ```typescript TypeScript theme={null}
  import { readFile } from "node:fs/promises";
  import type { ExtractedField } from "@anyformat/sdk";

  const run = await workflow.run(new File([await readFile("invoice.pdf")], "invoice.pdf"));
  const result = await run.wait();

  console.log(result.raw.classifications[0]?.category);                                   // "Invoice"
  console.log((result.field("total_amount") as ExtractedField<string> | undefined)?.value); // "4594.62"
  ```
</CodeGroup>

The run for a one-page invoice, trimmed. `classifications[].category` is the category **name**, and only the Extract on the taken branch produces fields:

```json Response highlight={4,8} theme={null}
{
  "results": {
    "classifications": [
      { "category": "Invoice", "confidence": 100.0, "evidence": "The document is explicitly titled \"INVOICE\" and includes invoice number INV-2026-9001, issue and due dates, itemized charges, subtotal, tax, and a total due of $4,594.62." }
    ],
    "extractions": [
      { "fields": {
        "invoice_number": { "value": "INV-2026-9001", "confidence": 98.0, "evidence": [{ "text": "Invoice #: INV-2026-9001", "page_number": 1 }] },
        "total_amount":   { "value": "4594.62",       "confidence": 99.0, "evidence": [{ "text": "TOTAL: $4,594.62", "page_number": 1 }] },
        "due_date":       { "value": "2026-03-22",    "confidence": 97.0, "evidence": [{ "text": "Due date: 2026-03-22", "page_number": 1 }] }
      } }
    ] } }
```

[Runs and results](/concepts/runs-and-results) has the full envelope, section by section.

## When it goes wrong

**Everything comes back as the same kind.** Classify picks the closest category it was given, so a thin description lets one category swallow the rest. Write each description the way a reader would tell the two apart, not as a label. "Proof of a completed payment" beats "receipt".

**A document that belongs to neither kind still gets a label.** Classify always picks one; it never returns nothing. Add an explicit "Other" category with its own description and leave its branch without an Extract. The document is then labelled, and no extraction is billed for it.

**Nothing reaches your Extract node.** The edge that leaves Classify carries the category **id** (`invoice`), not the category's display name (`Invoice`). The response reports the name in `classifications[].category`, so the two are easy to confuse. Check the `branch` on every edge against the `id` in `categories`.

**A scan comes back with poor text.** Parse runs on the Flash tier here. Flash reads the PDF's own text layer and makes no model call, which is enough for born-digital PDFs and keeps the workflow at 52 credits per page. Set `"mode": "standard"` on the Parse node when scans arrive, at 25 credits per page instead of 7.

**You are about to add a third Extract node.** Two categories can share one Extract when they need the same fields: point both branches at it. Add a node only where the fields differ.

## Next

* [Classify](/guides/nodes/classify) and [Split](/guides/nodes/split) for files that hold several documents.
* [Contract analysis](/examples/contract-analysis) to add Validate after an Extract.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.