> ## 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.

**Nodes used:** [Parse](/guides/nodes/parse), [Classify](/guides/nodes/classify), [Extract](/guides/nodes/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.

```
                          ┌─ invoice ─> [ Extract: invoice ]
[ Parse ] → [ Classify ] ─┤
                          └─ receipt ─> [ Extract: receipt ]
```

## 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 theme={null}
{
  "status": "processed",
  "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": [
      {
        "split_name": null,
        "partition": null,
        "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 }] }
        }
      }
    ]
  }
}
```

## Tips

* Write category descriptions as a reader would tell the two apart, not as a label. "Proof of a completed payment" beats "receipt".
* Two categories can share one Extract node when they need the same fields: point both branches at it.
* Flash parse is enough for born-digital PDFs and keeps this workflow at 7 + 10 + 35 credits per page. Move Parse to Standard when scans arrive.
* A document that matches no category is still classified: Classify always picks the closest one. Add an "Other" category with an explicit description when you need a place for the rest, and leave its branch without an Extract.

## 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.
