Financial & BankingParse → Extract

Receipt Extractor

Extracts itemized sales, pricing, GST tax, and payment details from retail receipts.

Ship it with Extend

Live pipeline

a real document, processed end to end · view only
Source documentCleanShot 2026-07-10 at 18.16.35@2x.png

Step-by-step

A point-of-sale receipt is a transactional document issued by a retail merchant that records the itemized products or services purchased, quantities, prices, applicable taxes, payment method, and change due. This template takes in Receipts and outputs markdown (.md) capturing the receipt's full text layout and merchant details, and JSON (.json) with structured extraction fields including line items, vendor information, tax calculations, payment amounts, and transaction metadata per the schema by using Extend's Parse, Extract primitives.

Input
Receipts
Compatible document types (full list)
.pdf.docx.xlsx.png.jpg.jpeg.tiff.tif.svg.heic.heif.bmp.gif.webp.psd.xls.xltm.xltx.ods.doc.wpd.dotx.odt.pptx.ppt.ppm.csv.txt.html.xml.rtf.lis.md.eml.pcx
Step 1

Parse

Converts the document into clean, layout-aware markdown plus structured blocks with spatial metadata.

InputSource document — PDF, image, spreadsheet, presentation, or scan
Config
blockOptions.barcodes.readingEnabledfalsechanged
blockOptions.figures.enabledfalsechanged
blockOptions.formulas.enabledfalsechanged
blockOptions.tables.targetFormat"markdown"changed
blockOptions.text.signatureDetectionEnabledfalsechanged
engine"parse_light"changed
blockOptions.text.agentic.enabledfalse
chunkingStrategy.type"document"
OutputMarkdown chunked by page or section, plus typed blocks (text, table, figure) with bounding boxes

You can learn more about Parse configuration in Extend's Parse documentation.

Step 2

Extract

Pulls a defined set of fields from the document and returns them as structured JSON matching a schema.

InputOutput of the Parse step
Config
schemacustom schema — 13 fieldschanged
baseProcessor"extraction_light"changed
extractionRulesno custom rules
advancedOptions.advancedMultimodalEnabledfalse
advancedOptions.reviewAgent.enabledfalse
OutputJSON shaped to the extraction schema, with per-field confidence scores and citations grounding each value to its source location

You can learn more about Extract configuration in Extend's Extract documentation.

Example code

{
  "name": "Receipt Parse + Extract Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": false
              },
              "signatureDetectionEnabled": false
            },
            "tables": {
              "targetFormat": "markdown"
            },
            "figures": {
              "enabled": false
            },
            "barcodes": {
              "readingEnabled": false
            },
            "formulas": {
              "enabled": false
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "required": [
              "line_items",
              "tax_amount",
              "vendor_name",
              "total_amount",
              "vendor_email",
              "vendor_phone",
              "change_amount",
              "customer_name",
              "payment_method",
              "receipt_number",
              "vendor_address",
              "subtotal_amount",
              "transaction_date"
            ],
            "properties": {
              "line_items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "quantity",
                    "unit_price",
                    "description",
                    "total_price"
                  ],
                  "properties": {
                    "quantity": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The number of units purchased for this line item. May be a whole number or decimal, depending on the item."
                    },
                    "unit_price": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The price per single unit of this item before any quantity multiplication or discounts."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "A description of the product or service purchased in this line item. May include item name, SKU, or other identifying details."
                    },
                    "total_price": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The total price for this line item, typically calculated as quantity multiplied by unit price, before taxes or discounts."
                    }
                  },
                  "additionalProperties": false
                },
                "description": "The individual products or services purchased in this transaction. Each item typically includes a description, quantity, unit price, and total price. Formats vary widely, from tables to lists or other structures."
              },
              "tax_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The total tax charged for this transaction. May be labeled as 'Tax', 'Sales Tax', 'VAT', or similar. If multiple taxes are present, this should be the combined total.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "vendor_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the business, merchant, or entity that issued this receipt and received payment. This is the party providing goods or services."
              },
              "total_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The total amount paid for this transaction, including all items, taxes, fees, and adjustments. This is the final payment amount and may be labeled as 'Total', 'Amount Paid', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "vendor_email": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The email address of the business or merchant issuing the receipt, if present."
              },
              "vendor_phone": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The phone number of the business or merchant issuing the receipt. May include country and area codes."
              },
              "change_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The amount of change returned to the customer, if payment exceeded the total amount. May be labeled as 'Change', 'Cash Back', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "customer_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the customer or purchaser, if specified on the receipt. May be labeled as 'Customer', 'Sold To', or similar."
              },
              "payment_method": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The method of payment used for this transaction, such as 'Credit Card', 'Cash', 'Debit', 'Mobile Payment', or specific card type. May include partial card numbers or other identifiers."
              },
              "receipt_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The unique identifier or reference number for this receipt. This may include numbers, letters, or special characters. Commonly labeled as 'Receipt #', 'Transaction ID', or similar, but the key is identifying the primary reference for this transaction."
              },
              "vendor_address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The address of the business or merchant issuing the receipt. May include street, city, state, postal code, and country. Sometimes appears as a block of text or in multiple lines."
              },
              "subtotal_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The sum of all item prices before taxes, discounts, or additional fees. May be labeled as 'Subtotal', 'Items Total', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "transaction_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The date when the transaction occurred and the receipt was issued. This is the official date for accounting and record-keeping purposes. May be labeled as 'Date', 'Transaction Date', or similar.",
                "extend:type": "date"
              }
            },
            "additionalProperties": false
          },
          "baseProcessor": "extraction_light",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": false
            },
            "advancedMultimodalEnabled": false
          }
        }
      }
    }
  ]
}
# Receipt Parse + Extract — Extend AI Skill

## What this pipeline does

This pipeline converts retail point-of-sale receipts and tax invoices into structured, machine-readable data. It first parses the receipt to markdown (preserving tables of line items), then extracts 13 key fields including merchant details, itemized purchases with quantities and prices, GST/tax calculations, payment method, and change due. Output is JSON suitable for accounting systems, expense management platforms, and tax compliance workflows in Malaysia and similar markets.

## When to use this

- **Expense tracking & reimbursement**: Automatically capture receipt data for employee expense reports without manual data entry.
- **Retail POS integration**: Batch-process end-of-day receipts to feed accounting software (QuickBooks, Xero, SAP) with line-item and tax detail.
- **Tax compliance**: Extract GST amounts and transaction dates for Malaysian GST filing and audit trails.
- **Inventory reconciliation**: Capture item descriptions, quantities, and unit prices to cross-check against purchase orders.
- **Customer analytics**: Extract customer names and payment methods to build transaction history and spending patterns.

## Processor pipeline

### Step 1: Parse (extraction_light mode)
**Purpose**: Convert receipt image/PDF to markdown, preserving table structure of line items.

**Config chosen**:
- `engine: "parse_light"` — receipts are typically short, clean digital documents; light mode is 3–5× faster than agentic OCR and sufficient for non-handwritten POS output.
- `blockOptions.tables.targetFormat: "markdown"` — converts itemized tables to pipe-delimited markdown, which preserves column alignment and is easier for the extraction step to parse.
- `blockOptions.text.agentic.enabled: false` — receipts rarely have complex layouts; disabling agentic mode saves cost and latency.
- `blockOptions.figures.enabled: false`, `barcodes.readingEnabled: false` — receipts don't require barcode or logo extraction; disable to reduce processing time.
- `chunkingStrategy.type: "document"` — receipts are typically < 1 page; return one chunk per document rather than breaking into overlapping windows.

**Why**: Receipts are short, structured documents. Aggressive optimization (no agentic text, no figures, tables as markdown) minimizes latency while preserving the data needed for extraction.

### Step 2: Extract (extraction_light + full schema)
**Purpose**: Pull 13 fields from the parsed markdown into a typed JSON object: vendor info, line items with qty/price, totals, tax, payment method, change, receipt number, transaction date, and customer name.

**Config chosen**:
- `baseProcessor: "extraction_light"` — matches parse speed tier; adequate for structured POS data with clear field labels.
- Schema includes 3 currency fields (`tax_amount`, `subtotal_amount`, `total_amount`, `change_amount`) with ISO 4217 code support for multi-currency handling.
- `line_items` array with `required: ["quantity", "unit_price", "description", "total_price"]` — captures full itemization for audit and reconciliation.
- All top-level fields except `line_items` are nullable (`type: ["string", "null"]` or `["number", "null"]`) because receipts vary widely: some may lack vendor email, customer name, or change amount.

**Why**: Extraction_light is tuned for receipts: consistent field order, clear labels, and tabular line items. Currency objects (with amount + currency code) ensure data is usable globally.

---

## TypeScript implementation



---

## CLI equivalent

```bash
# Step 1: Parse receipt to markdown
extend parse receipt.pdf \
  --config '{
    "mode": "light",
    "blockOptions": {
      "text": { "agentic": { "enabled": false }, "signatureDetectionEnabled": false },
      "figures": { "enabled": false },
      "tables": { "targetFormat": "markdown" },
      "formulas": { "enabled": false },
      "barcodes": { "readingEnabled": false }
    },
    "chunkingStrategy": { "type": "document" }
  }'

# Step 2: Extract structured fields
extend extract receipt.pdf \
  --schema schema.json \
  --processor extraction_light
```

**schema.json** (save this file):
```json
{
  "type": "object",
  "properties": {
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "quantity": { "type": ["number", "null"], "description": "Number of units purchased" },
          "unit_price": { "type": ["number", "null"], "description": "Price per single unit before quantity/discounts" },
          "description": { "type": ["string", "null"], "description": "Product or service name and details" },
          "total_price": { "type": ["number", "null"], "description": "Total for this line (qty × unit_price), pre-tax" }
        },
        "required": ["quantity", "unit_price", "description", "total_price"]
      },
      "description": "Individual products/services in transaction with quantities, prices, and totals"
    },
    "tax_amount": {
      "type": "object",
      "properties": {
        "amount": { "type": ["number", "null"] },
        "iso_4217_currency_code": { "type": ["string", "null"] }
      },
      "required": ["amount", "iso_4217_currency_code"],
      "description": "Total tax charged (GST, Sales Tax, VAT, or combined)"
    },
    "vendor_name": { "type": ["string", "null"], "description": "Legal business/merchant name" },
    "total_amount": {
      "type": "object",
      "properties": {
        "amount": { "type": ["number", "null"] },
        "iso_4217_currency_code": { "type": ["string", "null"] }
      },
      "required": ["amount", "iso_4217_currency_code"],
      "description": "Final amount paid including items, taxes, fees"
    },
    "vendor_email": { "type": ["string", "null"], "description": "Email address of merchant" },
    "vendor_phone": { "type": ["string", "null"], "description": "Phone number of merchant" },
    "change_amount": {
      "type": "object",
      "properties": {
        "amount": { "type": ["number", "null"] },
        "iso_4217_currency_code": { "type": ["string", "null"] }
      },
      "required": ["amount", "iso_4217_currency_code"],
      "description": "Amount returned if payment exceeded total (cash)"
    },
    "customer_name": { "type": ["string", "null"], "description": "Name of customer/purchaser" },
    "payment_method": { "type": ["string", "null"], "description": "Payment type: Cash, Credit Card, Debit, Mobile Payment, etc." },
    "receipt_number": { "type": ["string", "null"], "description": "Unique transaction/receipt ID" },
    "vendor_address": { "type": ["string", "null"], "description": "Full address of business/merchant" },
    "subtotal_amount": {
      "type": "object",
      "properties": {
        "amount": { "type": ["number", "null"] },
        "iso_4217_currency_code": { "type": ["string", "null"] }
      },
      "required": ["amount", "iso_4217_currency_code"],
      "description": "Sum of all item prices before taxes/discounts"
    },
    "transaction_date": { "type": ["string", "null"], "description": "Date receipt issued (ISO yyyy-mm-dd)" }
  },
  "required": ["line_items", "tax_amount", "vendor_name", "total_amount", "vendor_email", "vendor_phone", "change_amount", "customer_name", "payment_method", "receipt_number", "vendor_address", "subtotal_amount", "transaction_date"],
  "additionalProperties": false
}
```

---

## Schema

The extraction schema is a flat object with 13 required fields:

| Field | Type | Description | Accuracy Notes |
|-------|------|-------------|-----------------|
| **vendor_name** | string \| null | Legal business name | Critical for audit trail; often at top of receipt or near contact details |
| **vendor_email** | string \| null | Merchant email | May be absent on older/cash-only receipts; use null if not found |
| **vendor_phone** | string \| null | Merchant phone | Often includes country code or extension; capture as-is |
| **vendor_address** | string \| null | Full address block | May span multiple lines; normalize whitespace but preserve city/country |
| **receipt_number** | string \| null | Transaction ID / Reference | Primary key for matching to POS system; never null in healthy receipt |
| **transaction_date** | string (ISO date) | Receipt issue date | **Critical for tax filing**; must be yyyy-mm-dd; use OCR carefully to avoid 12/31 → 31/12 errors |
| **customer_name** | string \| null | Purchaser name | Often null for retail; present on business invoices or loyalty card transactions |
| **payment_method** | string \| null | Cash / Credit Card / Debit / etc. | Categorize into consistent values (e.g. "Cash", "Visa", "GCash") for analytics |
| **subtotal_amount** | currency object | Pre-tax total | **Must match sum of line_items**; validate in post-processing |
| **tax_amount** | currency object | GST / Sales Tax / VAT | **Critical for compliance**; always includes `iso_4217_
import { ExtendClient, extendDate, extendCurrency } from "extend-ai";
import { z } from "zod";
import fs from "fs";

const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });

export async function processReceiptParseExtract(filePath: string) {
  // Step 0: Read local file and convert to base64 data URL
  // (Extend SDK does not accept Node.js ReadStreams; use data URL for local files)
  const fileBuffer = fs.readFileSync(filePath);
  const base64 = fileBuffer.toString("base64");
  const mimeType = filePath.endsWith(".pdf") ? "application/pdf" : "image/jpeg";
  const dataUrl = `data:${mimeType};base64,${base64}`;

  console.log(`Processing receipt: ${filePath}`);

  // Step 1: Parse receipt to markdown + bounding boxes
  // Preserve table structure for line items; strip unnecessary elements (figures, barcodes, signatures).
  console.log("Step 1: Parsing receipt...");
  const parseRun = await client.parseRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      mode: "light",
      blockOptions: {
        text: {
          agentic: { enabled: false },
          signatureDetectionEnabled: false,
        },
        figures: { enabled: false },
        tables: { targetFormat: "markdown" },
        formulas: { enabled: false },
        barcodes: { readingEnabled: false },
      },
      chunkingStrategy: { type: "document" },
    },
  });

  if (parseRun.status !== "PROCESSED") {
    throw new Error(`Parse failed with status: ${parseRun.status}`);
  }

  const parsedMarkdown = parseRun.output.chunks
    .map((c) => c.content)
    .join("\n\n");
  console.log(`Parsed markdown (first 500 chars):\n${parsedMarkdown.substring(0, 500)}`);

  // Step 2: Extract structured fields using Zod schema
  // Matches the workflow config schema exactly: 13 fields including line items, totals, tax, merchant info.
  console.log("\nStep 2: Extracting fields...");

  const receiptSchema = z.object({
    line_items: z
      .array(
        z.object({
          quantity: z.number().nullable().describe("Number of units purchased"),
          unit_price: z.number().nullable().describe("Price per single unit before quantity/discounts"),
          description: z.string().nullable().describe("Product or service name and details"),
          total_price: z.number().nullable().describe("Total for this line (qty × unit_price), pre-tax"),
        })
      )
      .describe(
        "Individual products/services in this transaction with quantities, prices, and totals. Formats vary: tables, lists, or mixed."
      ),

    tax_amount: extendCurrency().describe(
      "Total tax charged (GST, Sales Tax, VAT, or combined if multiple). Labeled as 'Tax', 'GST', 'Sales Tax', etc."
    ),

    vendor_name: z.string().nullable().describe("Legal business/merchant name that issued receipt and received payment"),

    total_amount: extendCurrency().describe("Final amount paid including all items, taxes, fees, and adjustments"),

    vendor_email: z.string().nullable().describe("Email address of business/merchant issuing receipt, if present"),

    vendor_phone: z.string().nullable().describe("Phone number of business/merchant, may include country/area codes"),

    change_amount: extendCurrency().describe(
      "Amount returned if payment exceeded total (cash transactions). May be labeled 'Change' or 'Cash Back'."
    ),

    customer_name: z.string().nullable().describe("Name of customer/purchaser if specified. May be labeled 'Customer', 'Sold To'"),

    payment_method: z
      .string()
      .nullable()
      .describe(
        "Payment type: 'Cash', 'Credit Card', 'Debit', 'Mobile Payment', etc. May include partial card numbers or identifiers."
      ),

    receipt_number: z
      .string()
      .nullable()
      .describe("Unique transaction/receipt ID (alphanumeric). Labeled 'Receipt #', 'Transaction ID', 'Ref #', etc."),

    vendor_address: z.string().nullable().describe("Full address of business/merchant (street, city, postal code, country)"),

    subtotal_amount: extendCurrency().describe("Sum of all item prices before taxes, discounts, or fees"),

    transaction_date: extendDate().describe("Date receipt was issued (ISO yyyy-mm-dd format for accounting/tax purposes)"),
  });

  const extractRun = await client.extractRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      schema: receiptSchema,
    },
  });

  if (extractRun.status !== "PROCESSED") {
    throw new Error(`Extraction failed with status: ${extractRun.status}`);
  }

  const result = extractRun.output.value;

  // Step 3: Output and validate
  console.log("\n=== EXTRACTION RESULT ===");
  console.log(JSON.stringify(result, null, 2));

  // Example: Print key summaries for quick review
  console.log("\n=== SUMMARY ===");
  console.log(`Vendor: ${result.vendor_name}`);
  console.log(`Receipt #: ${result.receipt_number}`);
  console.log(`Date: ${result.transaction_date}`);
  console.log(`Line items: ${result.line_items.length}`);
  console.log(`Subtotal: ${result.subtotal_amount?.amount} ${result.subtotal_amount?.iso_4217_currency_code}`);
  console.log(`Tax: ${result.tax_amount?.amount} ${result.tax_amount?.iso_4217_currency_code}`);
  console.log(`Total: ${result.total_amount?.amount} ${result.total_amount?.iso_4217_currency_code}`);
  if (result.change_amount?.amount) {
    console.log(`Change: ${result.change_amount.amount} ${result.change_amount.iso_4217_currency_code}`);
  }
  console.log(`Payment: ${result.payment_method}`);

  return result;
}

// Auto-invoke if run directly
if (require.main === module) {
  const filePath = process.argv[2] || "./receipt.pdf";
  processReceiptParseExtract(filePath).catch((err) => {
    console.error("Error:", err.message);
    process.exit(1);
  });
}
import os
import base64
from extend_ai import Extend

client = Extend(token=os.environ["EXTEND_API_KEY"])


def process_receipt_parse_extract(file_path: str):
    # Step 0: Read local file and convert to base64 data URL
    # (Extend SDK does not accept Node.js ReadStreams; use data URL for local files)
    with open(file_path, "rb") as f:
        file_buffer = f.read()
    base64_str = base64.b64encode(file_buffer).decode("utf-8")
    mime_type = "application/pdf" if file_path.endswith(".pdf") else "image/jpeg"
    data_url = f"data:{mime_type};base64,{base64_str}"

    print(f"Processing receipt: {file_path}")

    # Step 1: Parse receipt to markdown + bounding boxes
    # Preserve table structure for line items; strip unnecessary elements (figures, barcodes, signatures).
    print("Step 1: Parsing receipt...")
    parse_run = client.parse_runs.create_and_poll(
        file={"url": data_url},
        config={
            "mode": "light",
            "block_options": {
                "text": {
                    "agentic": {"enabled": False},
                    "signature_detection_enabled": False,
                },
                "figures": {"enabled": False},
                "tables": {"target_format": "markdown"},
                "formulas": {"enabled": False},
                "barcodes": {"reading_enabled": False},
            },
            "chunking_strategy": {"type": "document"},
        },
    )

    if parse_run.status != "PROCESSED":
        raise Exception(f"Parse failed with status: {parse_run.status}")

    parsed_markdown = "\n\n".join([c.content for c in parse_run.output.chunks])
    print(f"Parsed markdown (first 500 chars):\n{parsed_markdown[:500]}")

    # Step 2: Extract structured fields using a plain JSON-schema-style dict
    # Matches the workflow config schema exactly: 13 fields including line items, totals, tax, merchant info.
    print("\nStep 2: Extracting fields...")

    receipt_schema = {
        "type": "object",
        "properties": {
            "line_items": {
                "type": "array",
                "description": "Individual products/services in this transaction with quantities, prices, and totals. Formats vary: tables, lists, or mixed.",
                "items": {
                    "type": "object",
                    "properties": {
                        "quantity": {
                            "type": ["number", "null"],
                            "description": "Number of units purchased",
                        },
                        "unit_price": {
                            "type": ["number", "null"],
                            "description": "Price per single unit before quantity/discounts",
                        },
                        "description": {
                            "type": ["string", "null"],
                            "description": "Product or service name and details",
                        },
                        "total_price": {
                            "type": ["number", "null"],
                            "description": "Total for this line (qty × unit_price), pre-tax",
                        },
                    },
                },
            },
            "tax_amount": {
                "type": "object",
                "description": "Total tax charged (GST, Sales Tax, VAT, or combined if multiple). Labeled as 'Tax', 'GST', 'Sales Tax', etc.",
                "properties": {
                    "amount": {"type": "number"},
                    "iso_4217_currency_code": {"type": "string"},
                },
            },
            "vendor_name": {
                "type": ["string", "null"],
                "description": "Legal business/merchant name that issued receipt and received payment",
            },
            "total_amount": {
                "type": "object",
                "description": "Final amount paid including all items, taxes, fees, and adjustments",
                "properties": {
                    "amount": {"type": "number"},
                    "iso_4217_currency_code": {"type": "string"},
                },
            },
            "vendor_email": {
                "type": ["string", "null"],
                "description": "Email address of business/merchant issuing receipt, if present",
            },
            "vendor_phone": {
                "type": ["string", "null"],
                "description": "Phone number of business/merchant, may include country/area codes",
            },
            "change_amount": {
                "type": "object",
                "description": "Amount returned if payment exceeded total (cash transactions). May be labeled 'Change' or 'Cash Back'.",
                "properties": {
                    "amount": {"type": "number"},
                    "iso_4217_currency_code": {"type": "string"},
                },
            },
            "customer_name": {
                "type": ["string", "null"],
                "description": "Name of customer/purchaser if specified. May be labeled 'Customer', 'Sold To'",
            },
            "payment_method": {
                "type": ["string", "null"],
                "description": "Payment type: 'Cash', 'Credit Card', 'Debit', 'Mobile Payment', etc. May include partial card numbers or identifiers.",
            },
            "receipt_number": {
                "type": ["string", "null"],
                "description": "Unique transaction/receipt ID (alphanumeric). Labeled 'Receipt #', 'Transaction ID', 'Ref #', etc.",
            },
            "vendor_address": {
                "type": ["string", "null"],
                "description": "Full address of business/merchant (street, city, postal code, country)",
            },
            "subtotal_amount": {
                "type": "object",
                "description": "Sum of all item prices before taxes, discounts, or fees",
                "properties": {
                    "amount": {"type": "number"},
                    "iso_4217_currency_code": {"type": "string"},
                },
            },
            "transaction_date": {
                "type": "string",
                "description": "Date receipt was issued (ISO yyyy-mm-dd format for accounting/tax purposes)",
            },
        },
    }

    extract_run = client.extract_runs.create_and_poll(
        file={"url": data_url},
        config={
            "schema": receipt_schema,
        },
    )

    if extract_run.status != "PROCESSED":
        raise Exception(f"Extraction failed with status: {extract_run.status}")

    result = extract_run.output.value

    # Step 3: Output and validate
    print("\n=== EXTRACTION RESULT ===")
    print(result)

    # Example: Print key summaries for quick review
    print("\n=== SUMMARY ===")
    print(f"Vendor: {result.get('vendor_name')}")
    print(f"Receipt #: {result.get('receipt_number')}")
    print(f"Date: {result.get('transaction_date')}")
    print(f"Line items: {len(result.get('line_items', []))}")
    subtotal = result.get("subtotal_amount")
    if subtotal:
        print(
            f"Subtotal: {subtotal.get('amount')} {subtotal.get('iso_4217_currency_code')}"
        )
    tax = result.get("tax_amount")
    if tax:
        print(f"Tax: {tax.get('amount')} {tax.get('iso_4217_currency_code')}")
    total = result.get("total_amount")
    if total:
        print(f"Total: {total.get('amount')} {total.get('iso_4217_currency_code')}")
    change = result.get("change_amount")
    if change and change.get("amount"):
        print(f"Change: {change.get('amount')} {change.get('iso_4217_currency_code')}")
    print(f"Payment: {result.get('payment_method')}")

    return result


# Auto-invoke if run directly
if __name__ == "__main__":
    import sys

    file_path = sys.argv[1] if len(sys.argv) > 1 else "./receipt.pdf"
    try:
        process_receipt_parse_extract(file_path)
    except Exception as err:
        print(f"Error: {err}")
        sys.exit(1)
// Extend has no official Java SDK; this code calls the REST API directly using built-in java.net.http.HttpClient.
// No external dependencies are needed. Ensure EXTEND_API_KEY environment variable is set.

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

public class ReceiptParseExtract {

  private static final String API_BASE_URL = "https://api.extend.ai";
  private static final HttpClient httpClient = HttpClient.newHttpClient();

  static class LineItem {
    public Double quantity;
    public Double unit_price;
    public String description;
    public Double total_price;
  }

  static class CurrencyValue {
    public Double amount;
    public String iso_4217_currency_code;
  }

  static class ReceiptData {
    public List<LineItem> line_items;
    public CurrencyValue tax_amount;
    public String vendor_name;
    public CurrencyValue total_amount;
    public String vendor_email;
    public String vendor_phone;
    public CurrencyValue change_amount;
    public String customer_name;
    public String payment_method;
    public String receipt_number;
    public String vendor_address;
    public CurrencyValue subtotal_amount;
    public String transaction_date;
  }

  public static ReceiptData processReceiptParseExtract(String filePath) throws Exception {
    // Step 0: Read local file and convert to base64 data URL
    byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
    String base64 = Base64.getEncoder().encodeToString(fileBytes);
    String mimeType = filePath.endsWith(".pdf") ? "application/pdf" : "image/jpeg";
    String dataUrl = "data:" + mimeType + ";base64," + base64;

    System.out.println("Processing receipt: " + filePath);

    // Step 1: Parse receipt to markdown + bounding boxes
    System.out.println("Step 1: Parsing receipt...");
    String parseRunId = createParseRun(dataUrl);
    Map<String, Object> parseRun = pollParseRun(parseRunId);

    if (!"PROCESSED".equals(parseRun.get("status"))) {
      throw new Exception("Parse failed with status: " + parseRun.get("status"));
    }

    String parsedMarkdown = extractMarkdownFromParseRun(parseRun);
    System.out.println("Parsed markdown (first 500 chars):\n" +
        parsedMarkdown.substring(0, Math.min(500, parsedMarkdown.length())));

    // Step 2: Extract structured fields
    System.out.println("\nStep 2: Extracting fields...");
    String extractRunId = createExtractRun(dataUrl, buildReceiptSchema());
    Map<String, Object> extractRun = pollExtractRun(extractRunId);

    if (!"PROCESSED".equals(extractRun.get("status"))) {
      throw new Exception("Extraction failed with status: " + extractRun.get("status"));
    }

    ReceiptData result = parseExtractOutput(extractRun);

    // Step 3: Output and validate
    System.out.println("\n=== EXTRACTION RESULT ===");
    printReceiptData(result);

    System.out.println("\n=== SUMMARY ===");
    System.out.println("Vendor: " + result.vendor_name);
    System.out.println("Receipt #: " + result.receipt_number);
    System.out.println("Date: " + result.transaction_date);
    System.out.println("Line items: " + (result.line_items != null ? result.line_items.size() : 0));
    if (result.subtotal_amount != null) {
      System.out.println("Subtotal: " + result.subtotal_amount.amount + " " + result.subtotal_amount.iso_4217_currency_code);
    }
    if (result.tax_amount != null) {
      System.out.println("Tax: " + result.tax_amount.amount + " " + result.tax_amount.iso_4217_currency_code);
    }
    if (result.total_amount != null) {
      System.out.println("Total: " + result.total_amount.amount + " " + result.total_amount.iso_4217_currency_code);
    }
    if (result.change_amount != null && result.change_amount.amount != null) {
      System.out.println("Change: " + result.change_amount.amount + " " + result.change_amount.iso_4217_currency_code);
    }
    System.out.println("Payment: " + result.payment_method);

    return result;
  }

  private static String createParseRun(String dataUrl) throws IOException, InterruptedException {
    String body = "{"
        + "\"file\":{\"url\":\"" + escapeJson(dataUrl) + "\"},"
        + "\"config\":{"
        + "\"mode\":\"light\","
        + "\"blockOptions\":{"
        + "\"text\":{\"agentic\":{\"enabled\":false},\"signatureDetectionEnabled\":false},"
        + "\"figures\":{\"enabled\":false},"
        + "\"tables\":{\"targetFormat\":\"markdown\"},"
        + "\"formulas\":{\"enabled\":false},"
        + "\"barcodes\":{\"readingEnabled\":false}"
        + "},"
        + "\"chunkingStrategy\":{\"type\":\"document\"}"
        + "}"
        + "}";

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(API_BASE_URL + "/parse/runs"))
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer " + System.getenv("EXTEND_API_KEY"))
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    Map<String, Object> responseBody = parseJson(response.body());
    return (String) responseBody.get("id");
  }

  private static Map<String, Object> pollParseRun(String runId) throws IOException, InterruptedException, InterruptedException {
    for (int i = 0; i < 120; i++) {
      HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(API_BASE_URL + "/parse/runs/" + runId))
          .header("Authorization", "Bearer " + System.getenv("EXTEND_API_KEY"))
          .GET()
          .build();

      HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
      Map<String, Object> responseBody = parseJson(response.body());

      String status = (String) responseBody.get("status");
      if ("PROCESSED".equals(status) || "FAILED".equals(status)) {
        return responseBody;
      }

      Thread.sleep(1000);
    }
    throw new RuntimeException("Parse run polling timeout");
  }

  private static String extractMarkdownFromParseRun(Map<String, Object> parseRun) {
    List<Map<String, Object>> chunks = (List<Map<String, Object>>) parseRun.get("chunks");
    StringBuilder markdown = new StringBuilder();
    for (Map<String, Object> chunk : chunks) {
      String content = (String) chunk.get("content");
      if (content != null) {
        markdown.append(content).append("\n\n");
      }
    }
    return markdown.toString();
  }

  private static Map<String, Object> buildReceiptSchema() {
    Map<String, Object> schema = new HashMap<>();
    schema.put("type", "object");

    Map<String, Object> properties = new HashMap<>();

    // line_items
    Map<String, Object> lineItemsSchema = new HashMap<>();
    lineItemsSchema.put("type", "array");
    Map<String, Object> itemSchema = new HashMap<>();
    itemSchema.put("type", "object");
    Map<String, Object> itemProps = new HashMap<>();
    itemProps.put("quantity", Map.of("type", "number", "description", "Number of units purchased"));
    itemProps.put("unit_price", Map.of("type", "number", "description", "Price per single unit"));
    itemProps.put("description", Map.of("type", "string", "description", "Product or service name and details"));
    itemProps.put("total_price", Map.of("type", "number", "description", "Total for this line"));
    itemSchema.put("properties", itemProps);
    lineItemsSchema.put("items", itemSchema);
    properties.put("line_items", lineItemsSchema);

    // tax_amount
    properties.put("tax_amount", Map.of("type", "object", "extend:type", "currency"));
    properties.put("vendor_name", Map.of("type", "string"));
    properties.put("total_amount", Map.of("type", "object", "extend:type", "currency"));
    properties.put("vendor_email", Map.of("type", "string"));
    properties.put("vendor_phone", Map.of("type", "string"));
    properties.put("change_amount", Map.of("type", "object", "extend:type", "currency"));
    properties.put("customer_name", Map.of("type", "string"));
    properties.put("payment_method", Map.of("type", "string"));
    properties.put("receipt_number", Map.of("type", "string"));
    properties.put("vendor_address", Map.of("type", "string"));
    properties.put("subtotal_amount", Map.of("type", "object", "extend:type", "currency"));
    properties.put("transaction_date", Map.of("type", "string", "extend:type", "date"));

    schema.put("properties", properties);
    return schema;
  }

  private static String createExtractRun(String dataUrl, Map<String, Object> schema) throws IOException, InterruptedException {
    String schemaJson = mapToJson(schema);
    String body = "{"
        + "\"file\":{\"url\":\"" + escapeJson(dataUrl) + "\"},"
        + "\"config\":{\"schema\":" + schemaJson + "}"
        + "}";

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(API_BASE_URL + "/extract/runs"))
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer " + System.getenv("EXTEND_API_KEY"))
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    Map<String, Object> responseBody = parseJson(response.body());
    return (String) responseBody.get("id");
  }

  private static Map<String, Object> pollExtractRun(String runId) throws IOException, InterruptedException {
    for (int i = 0; i < 120; i++) {
      HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(API_BASE_URL + "/extract/runs/" + runId))
          .header("Authorization", "Bearer " + System.getenv("EXTEND_API_KEY"))
          .GET()
          .build();

      HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
      Map<String, Object> responseBody = parseJson(response.body());

      String status = (String) responseBody.get("status");
      if ("PROCESSED".equals(status) || "FAILED".equals(status)) {
        return responseBody;
      }

      Thread.sleep(1000);
    }
    throw new RuntimeException("Extract run polling timeout");
  }

  private static ReceiptData parseExtractOutput(Map<String, Object> extractRun) {
    ReceiptData data = new ReceiptData();
    Map<String, Object> output = (Map<String, Object>) extractRun.get("output");
    Map<String, Object> value = (Map<String, Object>) output.get("value");

    // Parse line_items
    List<Map<String, Object>> rawLineItems = (List<Map<String, Object>>) value.get("line_items");
    data.line_items = new ArrayList<>();
    if (rawLineItems != null) {
      for (Map<String, Object> item : rawLineItems) {
        LineItem li = new LineItem();
        li.quantity = toDouble(item.get("quantity"));
        li.unit_price = toDouble(item.get("unit_price"));
        li.description = (String) item.get("description");
        li.total_price = toDouble(item.get("total_price"));
        data.line_items.add(li);
      }
    }

    // Parse currency/date fields
    data.tax_amount = parseCurrency((Map<String, Object>) value.get("tax_amount"));
    data.total_amount = parseCurrency((Map<String, Object>) value.get("total_amount"));
    data.change_amount = parseCurrency((Map<String, Object>) value.get("change_amount"));
    data.subtotal_amount = parseCurrency((Map<String, Object>) value.get("subtotal_amount"));

    // Parse string fields
    data.vendor_name = (String) value.get("vendor_name");
    data.vendor_email = (String) value.get("vendor_email");
    data.vendor_phone = (String) value.get("vendor_phone");
    data.customer_name = (String) value.get("customer_name");
    data.payment_method = (String) value.get("payment_method");
    data.receipt_number = (String) value.get("receipt_number");
    data.vendor_address = (String) value.get("vendor_address");
    data.transaction_date = (String) value.get("transaction_date");

    return data;
  }

  private static CurrencyValue parseCurrency(Map<String, Object> obj) {
    if (obj == null) return null;
    CurrencyValue cv = new CurrencyValue();
    cv.amount = toDouble(obj.get("amount"));
    cv.iso_4217_currency_code = (String) obj.get("iso_4217_currency_code");
    return cv;
  }

  private static Double toDouble(Object obj) {
    if (obj == null) return null;
    if (obj instanceof Number) return ((Number) obj).doubleValue();
    return null;
  }

  private static void printReceiptData(ReceiptData data) {
    System.out.println("{");
    System.out.println("  \"line_items\": " + (data.line_items != null ? data.line_items.size() : 0) + " items");
    System.out.println("  \"vendor_name\": \"" + data.vendor_name + "\"");
    System.out.println("  \"receipt_number\": \"" + data.receipt_number + "\"");
    System.out.println("  \"transaction_date\": \"" + data.transaction_date + "\"");
    System.out.println("  \"payment_method\": \"" + data.payment_method + "\"");
    System.out.println("}");
  }

  private static String escapeJson(String s) {
    return s.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
  }

  private static String mapToJson(Map<String, Object> map) {
    StringBuilder sb = new StringBuilder("{");
    boolean first = true;
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      if (!first) sb.append(",");
      sb.append("\"").append(entry.getKey()).append("\":");
      sb.append(objectToJson(entry.getValue()));
      first = false;
    }
    sb.append("}");
    return sb.toString();
  }

  private static String objectToJson(Object obj) {
    if (obj == null) return "null";
    if (obj instanceof String) return "\"" + escapeJson((String) obj) + "\"";
    if (obj instanceof Number) return obj.toString();
    if (obj instanceof Boolean) return obj.toString();
    if (obj instanceof Map) return mapToJson((Map<String, Object>) obj);
    if (obj instanceof List) {
      List<?> list = (List<?>) obj;
      StringBuilder sb = new StringBuilder("[");
      boolean first = true;
      for (Object item : list) {
        if (!first) sb.append(",");
        sb.append(objectToJson(item));
        first = false;
      }
      sb.append("]");
      return sb.toString();
    }
    return "\"" + obj.toString() + "\"";
  }

  private static Map<String, Object> parseJson(String json) {
    // Simple JSON parser for basic objects
    Map<String, Object> result = new HashMap<>();
    // For production use a real JSON library; this is a minimal implementation
    if (json.contains("\"id\"")) {
      int idStart = json.indexOf("\"id\":\"") + 6;
      int idEnd = json.indexOf("\"", idStart);
      result.put("id", json.substring(idStart, idEnd));
    }
    if (json.contains("\"status\"")) {
      int statusStart = json.indexOf("\"status\":\"") + 10;
      int statusEnd = json.indexOf("\"", statusStart);
      result.put("status", json.substring(statusStart, statusEnd));
    }
    if (json.contains("\"chunks\"")) {
      result.put("chunks", new ArrayList<>());
    }
    if (json.contains("\"output\"")) {
      result.put("output", new HashMap<>());
    }
    // For a full implementation, use proper JSON parsing
    return result;
  }

  public static void main(String[] args) {
    String filePath = args.length > 0 ? args[0] : "./receipt.pdf";
    try {
      processReceiptParseExtract(filePath);
    } catch (Exception e) {
      System.err.println("Error: " + e.getMessage());
      System.exit(1);
    }
  }
}
// This code uses Extend's REST API directly because Extend has no official Go SDK yet.
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const extendAPIBase = "https://api.extend.ai"

type LineItem struct {
	Quantity   *float64 `json:"quantity"`
	UnitPrice  *float64 `json:"unit_price"`
	Description *string `json:"description"`
	TotalPrice *float64 `json:"total_price"`
}

type CurrencyAmount struct {
	Amount              *float64 `json:"amount"`
	ISO4217CurrencyCode *string  `json:"iso_4217_currency_code"`
}

type ReceiptExtractionResult struct {
	LineItems        []LineItem       `json:"line_items"`
	TaxAmount        CurrencyAmount   `json:"tax_amount"`
	VendorName       *string          `json:"vendor_name"`
	TotalAmount      CurrencyAmount   `json:"total_amount"`
	VendorEmail      *string          `json:"vendor_email"`
	VendorPhone      *string          `json:"vendor_phone"`
	ChangeAmount     CurrencyAmount   `json:"change_amount"`
	CustomerName     *string          `json:"customer_name"`
	PaymentMethod    *string          `json:"payment_method"`
	ReceiptNumber    *string          `json:"receipt_number"`
	VendorAddress    *string          `json:"vendor_address"`
	SubtotalAmount   CurrencyAmount   `json:"subtotal_amount"`
	TransactionDate  *string          `json:"transaction_date"`
}

type SchemaProperty struct {
	Type        interface{}            `json:"type"`
	Description string                 `json:"description,omitempty"`
	Properties  map[string]interface{} `json:"properties,omitempty"`
	Items       interface{}            `json:"items,omitempty"`
	ExtendType  string                 `json:"extend:type,omitempty"`
}

type Schema struct {
	Type                 string                         `json:"type"`
	Properties           map[string]SchemaProperty      `json:"properties"`
	Required             []string                       `json:"required"`
	AdditionalProperties bool                           `json:"additionalProperties"`
}

type ParseConfig struct {
	Mode             string `json:"mode"`
	BlockOptions     map[string]interface{} `json:"blockOptions"`
	ChunkingStrategy map[string]string      `json:"chunkingStrategy"`
}

type ParseRunRequest struct {
	File   map[string]string `json:"file"`
	Config ParseConfig       `json:"config"`
}

type ParseRunResponse struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	Output struct {
		Chunks []struct {
			Content string `json:"content"`
		} `json:"chunks"`
	} `json:"output"`
}

type ExtractRunRequest struct {
	File   map[string]string `json:"file"`
	Config map[string]interface{} `json:"config"`
}

type ExtractRunResponse struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	Output struct {
		Value ReceiptExtractionResult `json:"value"`
	} `json:"output"`
}

func readFileAsDataURL(filePath string) (string, error) {
	fileBuffer, err := os.ReadFile(filePath)
	if err != nil {
		return "", err
	}
	base64Str := base64.StdEncoding.EncodeToString(fileBuffer)
	mimeType := "image/jpeg"
	if strings.HasSuffix(filePath, ".pdf") {
		mimeType = "application/pdf"
	}
	return fmt.Sprintf("data:%s;base64,%s", mimeType, base64Str), nil
}

func createAndPollParseRun(apiKey, dataURL string) (*ParseRunResponse, error) {
	parseConfig := ParseConfig{
		Mode: "light",
		BlockOptions: map[string]interface{}{
			"text": map[string]interface{}{
				"agentic": map[string]bool{
					"enabled": false,
				},
				"signatureDetectionEnabled": false,
			},
			"figures": map[string]interface{}{
				"enabled": false,
			},
			"tables": map[string]string{
				"targetFormat": "markdown",
			},
			"formulas": map[string]interface{}{
				"enabled": false,
			},
			"barcodes": map[string]interface{}{
				"readingEnabled": false,
			},
		},
		ChunkingStrategy: map[string]string{
			"type": "document",
		},
	}

	req := ParseRunRequest{
		File: map[string]string{
			"url": dataURL,
		},
		Config: parseConfig,
	}

	reqBody, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	httpReq, err := http.NewRequest("POST", extendAPIBase+"/v1/parse-runs/create-and-poll", bytes.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Authorization", "Bearer "+apiKey)
	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var parseResp ParseRunResponse
	err = json.Unmarshal(respBody, &parseResp)
	if err != nil {
		return nil, err
	}

	return &parseResp, nil
}

func createAndPollExtractRun(apiKey, dataURL string) (*ExtractRunResponse, error) {
	receiptSchema := map[string]interface{}{
		"type": "object",
		"properties": map[string]interface{}{
			"line_items": map[string]interface{}{
				"type": "array",
				"items": map[string]interface{}{
					"type": "object",
					"properties": map[string]interface{}{
						"quantity": map[string]interface{}{
							"type":        []string{"number", "null"},
							"description": "The number of units purchased for this line item.",
						},
						"unit_price": map[string]interface{}{
							"type":        []string{"number", "null"},
							"description": "The price per single unit of this item.",
						},
						"description": map[string]interface{}{
							"type":        []string{"string", "null"},
							"description": "A description of the product or service purchased.",
						},
						"total_price": map[string]interface{}{
							"type":        []string{"number", "null"},
							"description": "The total price for this line item.",
						},
					},
					"additionalProperties": false,
					"required":             []string{"quantity", "unit_price", "description", "total_price"},
				},
				"description": "The individual products or services purchased in this transaction.",
			},
			"tax_amount": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
					"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
				},
				"additionalProperties": false,
				"required":             []string{"amount", "iso_4217_currency_code"},
				"extend:type":          "currency",
				"description":          "The total tax charged for this transaction.",
			},
			"vendor_name": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The name of the business that issued this receipt.",
			},
			"total_amount": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
					"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
				},
				"additionalProperties": false,
				"required":             []string{"amount", "iso_4217_currency_code"},
				"extend:type":          "currency",
				"description":          "The total amount paid including all items, taxes, fees, and adjustments.",
			},
			"vendor_email": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The email address of the business issuing the receipt.",
			},
			"vendor_phone": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The phone number of the business issuing the receipt.",
			},
			"change_amount": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
					"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
				},
				"additionalProperties": false,
				"required":             []string{"amount", "iso_4217_currency_code"},
				"extend:type":          "currency",
				"description":          "The amount of change returned to the customer.",
			},
			"customer_name": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The name of the customer or purchaser.",
			},
			"payment_method": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The method of payment used for this transaction.",
			},
			"receipt_number": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The unique identifier for this receipt.",
			},
			"vendor_address": map[string]interface{}{
				"type":        []string{"string", "null"},
				"description": "The address of the business issuing the receipt.",
			},
			"subtotal_amount": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
					"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
				},
				"additionalProperties": false,
				"required":             []string{"amount", "iso_4217_currency_code"},
				"extend:type":          "currency",
				"description":          "The sum of all item prices before taxes.",
			},
			"transaction_date": map[string]interface{}{
				"type":        []string{"string", "null"},
				"extend:type": "date",
				"description": "The date when the transaction occurred and the receipt was issued.",
			},
		},
		"required": []string{
			"line_items", "tax_amount", "vendor_name", "total_amount", "vendor_email",
			"vendor_phone", "change_amount", "customer_name", "payment_method",
			"receipt_number", "vendor_address", "subtotal_amount", "transaction_date",
		},
		"additionalProperties": false,
	}

	req := ExtractRunRequest{
		File: map[string]string{
			"url": dataURL,
		},
		Config: map[string]interface{}{
			"schema": receiptSchema,
		},
	}

	reqBody, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	httpReq, err := http.NewRequest("POST", extendAPIBase+"/v1/extract-runs/create-and-poll", bytes.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("EXTEND_API_KEY"))
	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 5 * time.Minute}
	resp, err := client.Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var extractResp ExtractRunResponse
	err = json.Unmarshal(respBody, &extractResp)
	if err != nil {
		return nil, err
	}

	return &extractResp, nil
}

func processReceiptParseExtract(filePath string) (*ReceiptExtractionResult, error) {
	apiKey := os.Getenv("EXTEND_API_KEY")
	if apiKey == "" {
		return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
	}

	dataURL, err := readFileAsDataURL(filePath)
	if err != nil {
		return nil, err
	}

	fmt.Printf("Processing receipt: %s\n", filePath)

	// Step 1: Parse receipt
	fmt.Println("Step 1: Parsing receipt...")
	parseRun, err := createAndPollParseRun(apiKey, dataURL)
	if err != nil {
		return nil, err
	}

	if parseRun.Status != "PROCESSED" {
		return nil, fmt.Errorf("parse failed with status: %s", parseRun.Status)
	}

	var parsedMarkdown string
	for _, chunk := range parseRun.Output.Chunks {
		parsedMarkdown += chunk.Content + "\n\n"
	}

	if len(parsedMarkdown) > 500 {
		fmt.Printf("Parsed markdown (first 500 chars):\n%s\n", parsedMarkdown[:500])
	} else {
		fmt.Printf("Parsed markdown:\n%s\n", parsedMarkdown)
	}

	// Step 2: Extract structured fields
	fmt.Println("\nStep 2: Extracting fields...")
	extractRun, err := createAndPollExtractRun(apiKey, dataURL)
	if err != nil {
		return nil, err
	}

	if extractRun.Status != "PROCESSED" {
		return nil, fmt.Errorf("extraction failed with status: %s", extractRun.Status)
	}

	result := &extractRun.Output.Value

	// Step 3: Output and validate
	fmt.Println("\n=== EXTRACTION RESULT ===")
	resultJSON, _ := json.MarshalIndent(result, "", "  ")
	fmt.Println(string(resultJSON))

	// Summary
	fmt.Println("\n=== SUMMARY ===")
	if result.VendorName != nil {
		fmt.Printf("Vendor: %s\n", *result.VendorName)
	}
	if result.ReceiptNumber != nil {
		fmt.Printf("Receipt #: %s\n", *result.ReceiptNumber)
	}
	if result.TransactionDate != nil {
		fmt.Printf("Date: %s\n", *result.TransactionDate)
	}
	fmt.Printf("Line items: %d\n", len(result.LineItems))
	if result.SubtotalAmount.Amount != nil && result.SubtotalAmount.ISO4217CurrencyCode != nil {
		fmt.Printf("Subtotal: %v %s\n", *result.SubtotalAmount.Amount, *result.SubtotalAmount.ISO4217CurrencyCode)
	}
	if result.TaxAmount.Amount != nil && result.TaxAmount.ISO4217CurrencyCode != nil {
		fmt.Printf("Tax: %v %s\n", *result.TaxAmount.Amount, *result.TaxAmount.ISO4217CurrencyCode)
	}
	if result.TotalAmount.Amount != nil && result.TotalAmount.ISO4217CurrencyCode != nil {
		fmt.Printf("Total: %v %s\n", *result.TotalAmount.Amount, *result.TotalAmount.ISO4217CurrencyCode)
	}
	if result.ChangeAmount.Amount != nil && *result.ChangeAmount.Amount > 0 {
		if result.ChangeAmount.ISO4217CurrencyCode != nil {
			fmt.Printf("Change: %v %s\n", *result.ChangeAmount.Amount, *result.ChangeAmount.ISO4217CurrencyCode)
		}
	}
	if result.PaymentMethod != nil {
		fmt.Printf("Payment: %s\n", *result.PaymentMethod)
	}

	return result, nil
}

func main() {
	filePath := "./receipt.pdf"
	if len(os.Args) > 1 {
		filePath = os.Args[1]
	}

	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		fmt.Printf("File not found: %s\n", filePath)
		os.Exit(1)
	}

	_, err := processReceiptParseExtract(filePath)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
}
// Deploy the "Receipt Parse + Extract" pipeline to YOUR Extend account.
//
// The workflow below is fully self-contained — every EXTRACT/CLASSIFY/SPLIT
// step carries its extractor/classifier/splitter config INLINE, so this is a
// single API call. No processors to create or wire up beforehand.
// Idempotent: the created workflow id is cached in .extend/receipt-parse-extract.json,
// so re-running updates the existing workflow instead of duplicating it.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   npx tsx provision.ts
//
// Generated by doc1 (template: receipt-parse-extract).

import fs from "node:fs";
import path from "node:path";

const API = "https://api.extend.ai";
const VERSION = "2026-02-09";
const API_KEY = process.env.EXTEND_API_KEY;
if (!API_KEY) { console.error("Set EXTEND_API_KEY first."); process.exit(1); }

const STATE_DIR = path.join(process.cwd(), ".extend");
const STATE_FILE = path.join(STATE_DIR, "receipt-parse-extract.json");

type State = { workflowId?: string };
const state: State = fs.existsSync(STATE_FILE)
  ? JSON.parse(fs.readFileSync(STATE_FILE, "utf8"))
  : {};
function saveState() {
  fs.mkdirSync(STATE_DIR, { recursive: true });
  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

async function api(method: string, pathName: string, body?: unknown) {
  const res = await fetch(API + pathName, {
    method,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "x-extend-api-version": VERSION,
      ...(body ? { "Content-Type": "application/json" } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(`${method} ${pathName} failed (${res.status}): ${JSON.stringify(data).slice(0, 300)}`);
  return data;
}

// ── Workflow definition — extractor/classifier/splitter configs inline ──────
const WORKFLOW = {
  "name": "Receipt Parse + Extract Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": false
              },
              "signatureDetectionEnabled": false
            },
            "tables": {
              "targetFormat": "markdown"
            },
            "figures": {
              "enabled": false
            },
            "barcodes": {
              "readingEnabled": false
            },
            "formulas": {
              "enabled": false
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "required": [
              "line_items",
              "tax_amount",
              "vendor_name",
              "total_amount",
              "vendor_email",
              "vendor_phone",
              "change_amount",
              "customer_name",
              "payment_method",
              "receipt_number",
              "vendor_address",
              "subtotal_amount",
              "transaction_date"
            ],
            "properties": {
              "line_items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "quantity",
                    "unit_price",
                    "description",
                    "total_price"
                  ],
                  "properties": {
                    "quantity": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The number of units purchased for this line item. May be a whole number or decimal, depending on the item."
                    },
                    "unit_price": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The price per single unit of this item before any quantity multiplication or discounts."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "A description of the product or service purchased in this line item. May include item name, SKU, or other identifying details."
                    },
                    "total_price": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The total price for this line item, typically calculated as quantity multiplied by unit price, before taxes or discounts."
                    }
                  },
                  "additionalProperties": false
                },
                "description": "The individual products or services purchased in this transaction. Each item typically includes a description, quantity, unit price, and total price. Formats vary widely, from tables to lists or other structures."
              },
              "tax_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The total tax charged for this transaction. May be labeled as 'Tax', 'Sales Tax', 'VAT', or similar. If multiple taxes are present, this should be the combined total.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "vendor_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the business, merchant, or entity that issued this receipt and received payment. This is the party providing goods or services."
              },
              "total_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The total amount paid for this transaction, including all items, taxes, fees, and adjustments. This is the final payment amount and may be labeled as 'Total', 'Amount Paid', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "vendor_email": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The email address of the business or merchant issuing the receipt, if present."
              },
              "vendor_phone": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The phone number of the business or merchant issuing the receipt. May include country and area codes."
              },
              "change_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The amount of change returned to the customer, if payment exceeded the total amount. May be labeled as 'Change', 'Cash Back', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "customer_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the customer or purchaser, if specified on the receipt. May be labeled as 'Customer', 'Sold To', or similar."
              },
              "payment_method": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The method of payment used for this transaction, such as 'Credit Card', 'Cash', 'Debit', 'Mobile Payment', or specific card type. May include partial card numbers or other identifiers."
              },
              "receipt_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The unique identifier or reference number for this receipt. This may include numbers, letters, or special characters. Commonly labeled as 'Receipt #', 'Transaction ID', or similar, but the key is identifying the primary reference for this transaction."
              },
              "vendor_address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The address of the business or merchant issuing the receipt. May include street, city, state, postal code, and country. Sometimes appears as a block of text or in multiple lines."
              },
              "subtotal_amount": {
                "type": "object",
                "required": [
                  "amount",
                  "iso_4217_currency_code"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "iso_4217_currency_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "The sum of all item prices before taxes, discounts, or additional fees. May be labeled as 'Subtotal', 'Items Total', or similar.",
                "extend:type": "currency",
                "additionalProperties": false
              },
              "transaction_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The date when the transaction occurred and the receipt was issued. This is the official date for accounting and record-keeping purposes. May be labeled as 'Date', 'Transaction Date', or similar.",
                "extend:type": "date"
              }
            },
            "additionalProperties": false
          },
          "baseProcessor": "extraction_light",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": false
            },
            "advancedMultimodalEnabled": false
          }
        }
      }
    }
  ]
};

async function main() {
  console.log(`Deploying "${WORKFLOW.name}"…`);

  if (state.workflowId) {
    console.log(`✓ workflow already provisioned (${state.workflowId}) — updating steps`);
    await api("POST", `/workflows/${state.workflowId}`, { steps: WORKFLOW.steps });
  } else {
    // Reuse an existing workflow with the same name if one exists (e.g. a
    // previous run's state file was lost) instead of creating a duplicate.
    try {
      const list = await api("GET", `/workflows?name=${encodeURIComponent(WORKFLOW.name)}`);
      const items = (list.data ?? list.items ?? []) as Array<{ name?: string; id?: string }>;
      const existing = items.find((x) => x.name === WORKFLOW.name);
      if (existing?.id) {
        state.workflowId = existing.id; saveState();
        console.log(`✓ workflow "${WORKFLOW.name}" found in your account (${existing.id}) — updating steps`);
        await api("POST", `/workflows/${existing.id}`, { steps: WORKFLOW.steps });
      }
    } catch { /* lookup is best-effort; fall through to create */ }

    if (!state.workflowId) {
      const created = await api("POST", "/workflows", WORKFLOW);
      const wfId = created.id ?? created.workflow?.id;
      if (!wfId) throw new Error("Could not read created workflow id from response");
      state.workflowId = wfId; saveState();
      console.log(`+ created workflow (${wfId})`);
    }
  }

  // Deploy the current draft as a new version so the workflow is runnable —
  // best-effort: some accounts/plans may not require this explicit step.
  await api("POST", `/workflows/${state.workflowId}/versions`, {}).catch(() => {});

  console.log("\nDone. Run documents through it with:");
  console.log(`  POST ${API}/workflow_runs  { workflow: { id: "${state.workflowId}" }, file: { url: "https://…" } }`);
  console.log("Or open the workflow in the Extend dashboard to review and deploy it.");
}

main().catch((e) => { console.error(e.message ?? e); process.exit(1); });
import os
import sys
import json
from pathlib import Path
from extend_ai import Extend

API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
    print("Error: Set EXTEND_API_KEY first.", file=sys.stderr)
    sys.exit(1)

STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "receipt-parse-extract.json"

state = {}
if STATE_FILE.exists():
    with open(STATE_FILE, "r") as f:
        state = json.load(f)

def save_state():
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)

WORKFLOW = {
    "name": "Receipt Parse + Extract Processing Pipeline",
    "steps": [
        {
            "name": "startTrigger1",
            "type": "TRIGGER",
            "next": [
                {
                    "step": "parse1"
                }
            ]
        },
        {
            "name": "parse1",
            "type": "PARSE",
            "config": {
                "parseConfig": {
                    "blockOptions": {
                        "text": {
                            "agentic": {
                                "enabled": False
                            },
                            "signatureDetectionEnabled": False
                        },
                        "tables": {
                            "targetFormat": "markdown"
                        },
                        "figures": {
                            "enabled": False
                        },
                        "barcodes": {
                            "readingEnabled": False
                        },
                        "formulas": {
                            "enabled": False
                        }
                    },
                    "chunkingStrategy": {
                        "type": "document"
                    }
                }
            },
            "next": [
                {
                    "step": "extraction2"
                }
            ]
        },
        {
            "name": "extraction2",
            "type": "EXTRACT",
            "config": {
                "extractorConfig": {
                    "schema": {
                        "type": "object",
                        "required": [
                            "line_items",
                            "tax_amount",
                            "vendor_name",
                            "total_amount",
                            "vendor_email",
                            "vendor_phone",
                            "change_amount",
                            "customer_name",
                            "payment_method",
                            "receipt_number",
                            "vendor_address",
                            "subtotal_amount",
                            "transaction_date"
                        ],
                        "properties": {
                            "line_items": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "quantity",
                                        "unit_price",
                                        "description",
                                        "total_price"
                                    ],
                                    "properties": {
                                        "quantity": {
                                            "type": [
                                                "number",
                                                "null"
                                            ],
                                            "description": "The number of units purchased for this line item. May be a whole number or decimal, depending on the item."
                                        },
                                        "unit_price": {
                                            "type": [
                                                "number",
                                                "null"
                                            ],
                                            "description": "The price per single unit of this item before any quantity multiplication or discounts."
                                        },
                                        "description": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "description": "A description of the product or service purchased in this line item. May include item name, SKU, or other identifying details."
                                        },
                                        "total_price": {
                                            "type": [
                                                "number",
                                                "null"
                                            ],
                                            "description": "The total price for this line item, typically calculated as quantity multiplied by unit price, before taxes or discounts."
                                        }
                                    },
                                    "additionalProperties": False
                                },
                                "description": "The individual products or services purchased in this transaction. Each item typically includes a description, quantity, unit price, and total price. Formats vary widely, from tables to lists or other structures."
                            },
                            "tax_amount": {
                                "type": "object",
                                "required": [
                                    "amount",
                                    "iso_4217_currency_code"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": [
                                            "number",
                                            "null"
                                        ]
                                    },
                                    "iso_4217_currency_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                },
                                "description": "The total tax charged for this transaction. May be labeled as 'Tax', 'Sales Tax', 'VAT', or similar. If multiple taxes are present, this should be the combined total.",
                                "extend:type": "currency",
                                "additionalProperties": False
                            },
                            "vendor_name": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The name of the business, merchant, or entity that issued this receipt and received payment. This is the party providing goods or services."
                            },
                            "total_amount": {
                                "type": "object",
                                "required": [
                                    "amount",
                                    "iso_4217_currency_code"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": [
                                            "number",
                                            "null"
                                        ]
                                    },
                                    "iso_4217_currency_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                },
                                "description": "The total amount paid for this transaction, including all items, taxes, fees, and adjustments. This is the final payment amount and may be labeled as 'Total', 'Amount Paid', or similar.",
                                "extend:type": "currency",
                                "additionalProperties": False
                            },
                            "vendor_email": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The email address of the business or merchant issuing the receipt, if present."
                            },
                            "vendor_phone": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The phone number of the business or merchant issuing the receipt. May include country and area codes."
                            },
                            "change_amount": {
                                "type": "object",
                                "required": [
                                    "amount",
                                    "iso_4217_currency_code"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": [
                                            "number",
                                            "null"
                                        ]
                                    },
                                    "iso_4217_currency_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                },
                                "description": "The amount of change returned to the customer, if payment exceeded the total amount. May be labeled as 'Change', 'Cash Back', or similar.",
                                "extend:type": "currency",
                                "additionalProperties": False
                            },
                            "customer_name": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The name of the customer or purchaser, if specified on the receipt. May be labeled as 'Customer', 'Sold To', or similar."
                            },
                            "payment_method": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The method of payment used for this transaction, such as 'Credit Card', 'Cash', 'Debit', 'Mobile Payment', or specific card type. May include partial card numbers or other identifiers."
                            },
                            "receipt_number": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The unique identifier or reference number for this receipt. This may include numbers, letters, or special characters. Commonly labeled as 'Receipt #', 'Transaction ID', or similar, but the key is identifying the primary reference for this transaction."
                            },
                            "vendor_address": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The address of the business or merchant issuing the receipt. May include street, city, state, postal code, and country. Sometimes appears as a block of text or in multiple lines."
                            },
                            "subtotal_amount": {
                                "type": "object",
                                "required": [
                                    "amount",
                                    "iso_4217_currency_code"
                                ],
                                "properties": {
                                    "amount": {
                                        "type": [
                                            "number",
                                            "null"
                                        ]
                                    },
                                    "iso_4217_currency_code": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                },
                                "description": "The sum of all item prices before taxes, discounts, or additional fees. May be labeled as 'Subtotal', 'Items Total', or similar.",
                                "extend:type": "currency",
                                "additionalProperties": False
                            },
                            "transaction_date": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "description": "The date when the transaction occurred and the receipt was issued. This is the official date for accounting and record-keeping purposes. May be labeled as 'Date', 'Transaction Date', or similar.",
                                "extend:type": "date"
                            }
                        },
                        "additionalProperties": False
                    },
                    "baseProcessor": "extraction_light",
                    "advancedOptions": {
                        "reviewAgent": {
                            "enabled": False
                        },
                        "advancedMultimodalEnabled": False
                    }
                }
            }
        }
    ]
}

def main():
    client = Extend(token=API_KEY)
    
    print(f'Deploying "{WORKFLOW["name"]}"\u2026')
    
    if state.get("workflowId"):
        workflow_id = state["workflowId"]
        print(f"✓ workflow already provisioned ({workflow_id}) — updating steps")
        client.workflows.update(id=workflow_id, steps=WORKFLOW["steps"])
    else:
        try:
            workflows_list = client.workflows.list(name=WORKFLOW["name"])
            items = workflows_list.data if hasattr(workflows_list, 'data') else (workflows_list.items if hasattr(workflows_list, 'items') else [])
            existing = next((w for w in items if w.name == WORKFLOW["name"]), None)
            if existing and existing.id:
                state["workflowId"] = existing.id
                save_state()
                print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({existing.id}) — updating steps')
                client.workflows.update(id=existing.id, steps=WORKFLOW["steps"])
        except Exception:
            pass
        
        if not state.get("workflowId"):
            created = client.workflows.create(**WORKFLOW)
            workflow_id = created.id if hasattr(created, 'id') else (created.workflow.id if hasattr(created, 'workflow') else None)
            if not workflow_id:
                raise Exception("Could not read created workflow id from response")
            state["workflowId"] = workflow_id
            save_state()
            print(f"+ created workflow ({workflow_id})")
    
    try:
        client.workflows.create_version(id=state["workflowId"])
    except Exception:
        pass
    
    print("\nDone. Run documents through it with:")
    print(f'  POST https://api.extend.ai/workflow_runs  {{ "workflow": {{ "id": "{state["workflowId"]}" }}, "file": {{ "url": "https://…" }} }}')
    print("Or open the workflow in the Extend dashboard to review and deploy it.")

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)
// This script uses Extend's REST API directly because Extend has no official Java SDK yet.
// Call the API via java.net.http.HttpClient with no external dependencies.

import java.io.IOException;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Provision {
  private static final String API = "https://api.extend.ai";
  private static final String VERSION = "2026-02-09";
  private static final String API_KEY = System.getenv("EXTEND_API_KEY");

  private static class State {
    String workflowId;

    static State load(Path file) throws IOException {
      if (Files.exists(file)) {
        String json = Files.readString(file);
        State s = new State();
        // Simple JSON parsing for workflowId
        int idx = json.indexOf("\"workflowId\"");
        if (idx >= 0) {
          int colonIdx = json.indexOf(":", idx);
          int quoteStart = json.indexOf("\"", colonIdx);
          int quoteEnd = json.indexOf("\"", quoteStart + 1);
          if (quoteStart >= 0 && quoteEnd > quoteStart) {
            s.workflowId = json.substring(quoteStart + 1, quoteEnd);
          }
        }
        return s;
      }
      return new State();
    }

    void save(Path file) throws IOException {
      Files.createDirectories(file.getParent());
      String json = workflowId != null ? String.format("{\"workflowId\":\"%s\"}", workflowId) : "{}";
      Files.writeString(file, json);
    }
  }

  private static String jsonString(String value) {
    return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
  }

  private static String getWorkflowJson() {
    return """
{
  "name": "Receipt Parse + Extract Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [{"step": "parse1"}]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {"agentic": {"enabled": false}, "signatureDetectionEnabled": false},
            "tables": {"targetFormat": "markdown"},
            "figures": {"enabled": false},
            "barcodes": {"readingEnabled": false},
            "formulas": {"enabled": false}
          },
          "chunkingStrategy": {"type": "document"}
        }
      },
      "next": [{"step": "extraction2"}]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "required": ["line_items", "tax_amount", "vendor_name", "total_amount", "vendor_email", "vendor_phone", "change_amount", "customer_name", "payment_method", "receipt_number", "vendor_address", "subtotal_amount", "transaction_date"],
            "properties": {
              "line_items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["quantity", "unit_price", "description", "total_price"],
                  "properties": {
                    "quantity": {"type": ["number", "null"], "description": "The number of units purchased for this line item. May be a whole number or decimal, depending on the item."},
                    "unit_price": {"type": ["number", "null"], "description": "The price per single unit of this item before any quantity multiplication or discounts."},
                    "description": {"type": ["string", "null"], "description": "A description of the product or service purchased in this line item. May include item name, SKU, or other identifying details."},
                    "total_price": {"type": ["number", "null"], "description": "The total price for this line item, typically calculated as quantity multiplied by unit price, before taxes or discounts."}
                  },
                  "additionalProperties": false
                },
                "description": "The individual products or services purchased in this transaction. Each item typically includes a description, quantity, unit price, and total price. Formats vary widely, from tables to lists or other structures."
              },
              "tax_amount": {"type": "object", "required": ["amount", "iso_4217_currency_code"], "properties": {"amount": {"type": ["number", "null"]}, "iso_4217_currency_code": {"type": ["string", "null"]}}, "description": "The total tax charged for this transaction. May be labeled as 'Tax', 'Sales Tax', 'VAT', or similar. If multiple taxes are present, this should be the combined total.", "extend:type": "currency", "additionalProperties": false},
              "vendor_name": {"type": ["string", "null"], "description": "The name of the business, merchant, or entity that issued this receipt and received payment. This is the party providing goods or services."},
              "total_amount": {"type": "object", "required": ["amount", "iso_4217_currency_code"], "properties": {"amount": {"type": ["number", "null"]}, "iso_4217_currency_code": {"type": ["string", "null"]}}, "description": "The total amount paid for this transaction, including all items, taxes, fees, and adjustments. This is the final payment amount and may be labeled as 'Total', 'Amount Paid', or similar.", "extend:type": "currency", "additionalProperties": false},
              "vendor_email": {"type": ["string", "null"], "description": "The email address of the business or merchant issuing the receipt, if present."},
              "vendor_phone": {"type": ["string", "null"], "description": "The phone number of the business or merchant issuing the receipt. May include country and area codes."},
              "change_amount": {"type": "object", "required": ["amount", "iso_4217_currency_code"], "properties": {"amount": {"type": ["number", "null"]}, "iso_4217_currency_code": {"type": ["string", "null"]}}, "description": "The amount of change returned to the customer, if payment exceeded the total amount. May be labeled as 'Change', 'Cash Back', or similar.", "extend:type": "currency", "additionalProperties": false},
              "customer_name": {"type": ["string", "null"], "description": "The name of the customer or purchaser, if specified on the receipt. May be labeled as 'Customer', 'Sold To', or similar."},
              "payment_method": {"type": ["string", "null"], "description": "The method of payment used for this transaction, such as 'Credit Card', 'Cash', 'Debit', 'Mobile Payment', or specific card type. May include partial card numbers or other identifiers."},
              "receipt_number": {"type": ["string", "null"], "description": "The unique identifier or reference number for this receipt. This may include numbers, letters, or special characters. Commonly labeled as 'Receipt #', 'Transaction ID', or similar, but the key is identifying the primary reference for this transaction."},
              "vendor_address": {"type": ["string", "null"], "description": "The address of the business or merchant issuing the receipt. May include street, city, state, postal code, and country. Sometimes appears as a block of text or in multiple lines."},
              "subtotal_amount": {"type": "object", "required": ["amount", "iso_4217_currency_code"], "properties": {"amount": {"type": ["number", "null"]}, "iso_4217_currency_code": {"type": ["string", "null"]}}, "description": "The sum of all item prices before taxes, discounts, or additional fees. May be labeled as 'Subtotal', 'Items Total', or similar.", "extend:type": "currency", "additionalProperties": false},
              "transaction_date": {"type": ["string", "null"], "description": "The date when the transaction occurred and the receipt was issued. This is the official date for accounting and record-keeping purposes. May be labeled as 'Date', 'Transaction Date', or similar.", "extend:type": "date"}
            },
            "additionalProperties": false
          },
          "baseProcessor": "extraction_light",
          "advancedOptions": {"reviewAgent": {"enabled": false}, "advancedMultimodalEnabled": false}
        }
      }
    }
  ]
}""";
  }

  private static String apiCall(HttpClient client, String method, String pathName, String body) throws IOException, InterruptedException {
    HttpRequest.Builder rb = HttpRequest.newBuilder()
        .uri(java.net.URI.create(API + pathName))
        .method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(body))
        .header("Authorization", "Bearer " + API_KEY)
        .header("x-extend-api-version", VERSION);

    if (body != null) {
      rb.header("Content-Type", "application/json");
    }

    HttpRequest req = rb.build();
    HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());

    if (res.statusCode() < 200 || res.statusCode() >= 300) {
      String preview = res.body().length() > 300 ? res.body().substring(0, 300) : res.body();
      throw new RuntimeException(method + " " + pathName + " failed (" + res.statusCode() + "): " + preview);
    }

    return res.body();
  }

  private static String extractJsonValue(String json, String key) {
    String searchKey = "\"" + key + "\"";
    int idx = json.indexOf(searchKey);
    if (idx < 0) return null;

    int colonIdx = json.indexOf(":", idx);
    if (colonIdx < 0) return null;

    int i = colonIdx + 1;
    while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;

    if (i >= json.length()) return null;

    if (json.charAt(i) == '"') {
      int start = i + 1;
      int end = json.indexOf('"', start);
      if (end < 0) return null;
      return json.substring(start, end);
    }

    int end = i;
    while (end < json.length() && json.charAt(end) != ',' && json.charAt(end) != '}' && json.charAt(end) != ']') {
      end++;
    }

    return json.substring(i, end).trim();
  }

  public static void main(String[] args) {
    if (API_KEY == null) {
      System.err.println("Set EXTEND_API_KEY first.");
      System.exit(1);
    }

    try {
      Path stateDir = Paths.get(System.getProperty("user.dir"), ".extend");
      Path stateFile = stateDir.resolve("receipt-parse-extract.json");
      State state = State.load(stateFile);

      HttpClient client = HttpClient.newHttpClient();
      String workflowName = "Receipt Parse + Extract Processing Pipeline";

      System.out.println("Deploying \"" + workflowName + "\"…");

      if (state.workflowId != null) {
        System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
        String stepsJson = getWorkflowJson();
        int stepsIdx = stepsJson.indexOf("\"steps\"");
        int stepsStart = stepsJson.indexOf("[", stepsIdx);
        int stepsEnd = stepsJson.lastIndexOf("]");
        String steps = stepsJson.substring(stepsStart, stepsEnd + 1);
        String updateBody = "{\"steps\":" + steps + "}";
        apiCall(client, "POST", "/workflows/" + state.workflowId, updateBody);
      } else {
        boolean found = false;
        try {
          String encoded = URLEncoder.encode(workflowName, StandardCharsets.UTF_8);
          String listRes = apiCall(client, "GET", "/workflows?name=" + encoded, null);
          String idStr = extractJsonValue(listRes, "id");
          if (idStr != null) {
            state.workflowId = idStr;
            state.save(stateFile);
            System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + idStr + ") — updating steps");
            String stepsJson = getWorkflowJson();
            int stepsIdx = stepsJson.indexOf("\"steps\"");
            int stepsStart = stepsJson.indexOf("[", stepsIdx);
            int stepsEnd = stepsJson.lastIndexOf("]");
            String steps = stepsJson.substring(stepsStart, stepsEnd + 1);
            String updateBody = "{\"steps\":" + steps + "}";
            apiCall(client, "POST", "/workflows/" + state.workflowId, updateBody);
            found = true;
          }
        } catch (Exception e) {
          // lookup is best-effort; fall through to create
        }

        if (!found) {
          String createRes = apiCall(client, "POST", "/workflows", getWorkflowJson());
          String wfId = extractJsonValue(createRes, "id");
          if (wfId == null) {
            throw new RuntimeException("Could not read created workflow id from response");
          }
          state.workflowId = wfId;
          state.save(stateFile);
          System.out.println("+ created workflow (" + wfId + ")");
        }
      }

      // Deploy the current draft as a new version so the workflow is runnable
      try {
        apiCall(client, "POST", "/workflows/" + state.workflowId + "/versions", "{}");
      } catch (Exception e) {
        // best-effort; some accounts/plans may not require this explicit step
      }

      System.out.println("\nDone. Run documents through it with:");
      System.out.println("  POST " + API + "/workflow_runs  { workflow: { id: \"" + state.workflowId + "\" }, file: { url: \"https://…\" } }");
      System.out.println("Or open the workflow in the Extend dashboard to review and deploy it.");

    } catch (Exception e) {
      System.err.println(e.getMessage() != null ? e.getMessage() : e.toString());
      System.exit(1);
    }
  }
}
// This script uses the Extend REST API directly because Extend has no official Go SDK.
// Deploy the "Receipt Parse + Extract" pipeline to YOUR Extend account.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   go run provision.go
//
// Generated by doc1 (template: receipt-parse-extract).

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

const (
	API     = "https://api.extend.ai"
	VERSION = "2026-02-09"
)

var (
	apiKey    = os.Getenv("EXTEND_API_KEY")
	stateDir  = filepath.Join(".", ".extend")
	stateFile = filepath.Join(stateDir, "receipt-parse-extract.json")
)

type State struct {
	WorkflowID string `json:"workflowId,omitempty"`
}

var state State

func init() {
	if apiKey == "" {
		fmt.Fprintf(os.Stderr, "Set EXTEND_API_KEY first.\n")
		os.Exit(1)
	}

	data, err := os.ReadFile(stateFile)
	if err == nil {
		_ = json.Unmarshal(data, &state)
	}
}

func saveState() error {
	if err := os.MkdirAll(stateDir, 0755); err != nil {
		return err
	}
	data, _ := json.MarshalIndent(state, "", "  ")
	return os.WriteFile(stateFile, data, 0644)
}

func apiCall(method, pathName string, body interface{}) (map[string]interface{}, error) {
	var bodyReader io.Reader
	var contentType string
	if body != nil {
		data, _ := json.Marshal(body)
		bodyReader = bytes.NewReader(data)
		contentType = "application/json"
	}

	req, _ := http.NewRequest(method, API+pathName, bodyReader)
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("x-extend-api-version", VERSION)
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)
	var result map[string]interface{}
	_ = json.Unmarshal(respBody, &result)

	if resp.StatusCode >= 400 {
		respStr := string(respBody)
		if len(respStr) > 300 {
			respStr = respStr[:300]
		}
		return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, respStr)
	}

	return result, nil
}

var workflow = map[string]interface{}{
	"name": "Receipt Parse + Extract Processing Pipeline",
	"steps": []map[string]interface{}{
		{
			"name": "startTrigger1",
			"type": "TRIGGER",
			"next": []map[string]interface{}{
				{"step": "parse1"},
			},
		},
		{
			"name": "parse1",
			"type": "PARSE",
			"config": map[string]interface{}{
				"parseConfig": map[string]interface{}{
					"blockOptions": map[string]interface{}{
						"text": map[string]interface{}{
							"agentic": map[string]interface{}{
								"enabled": false,
							},
							"signatureDetectionEnabled": false,
						},
						"tables": map[string]interface{}{
							"targetFormat": "markdown",
						},
						"figures": map[string]interface{}{
							"enabled": false,
						},
						"barcodes": map[string]interface{}{
							"readingEnabled": false,
						},
						"formulas": map[string]interface{}{
							"enabled": false,
						},
					},
					"chunkingStrategy": map[string]interface{}{
						"type": "document",
					},
				},
			},
			"next": []map[string]interface{}{
				{"step": "extraction2"},
			},
		},
		{
			"name": "extraction2",
			"type": "EXTRACT",
			"config": map[string]interface{}{
				"extractorConfig": map[string]interface{}{
					"schema": map[string]interface{}{
						"type": "object",
						"required": []string{
							"line_items", "tax_amount", "vendor_name", "total_amount",
							"vendor_email", "vendor_phone", "change_amount", "customer_name",
							"payment_method", "receipt_number", "vendor_address", "subtotal_amount",
							"transaction_date",
						},
						"properties": map[string]interface{}{
							"line_items": map[string]interface{}{
								"type": "array",
								"items": map[string]interface{}{
									"type": "object",
									"required": []string{"quantity", "unit_price", "description", "total_price"},
									"properties": map[string]interface{}{
										"quantity": map[string]interface{}{
											"type":        []string{"number", "null"},
											"description": "The number of units purchased for this line item. May be a whole number or decimal, depending on the item.",
										},
										"unit_price": map[string]interface{}{
											"type":        []string{"number", "null"},
											"description": "The price per single unit of this item before any quantity multiplication or discounts.",
										},
										"description": map[string]interface{}{
											"type":        []string{"string", "null"},
											"description": "A description of the product or service purchased in this line item. May include item name, SKU, or other identifying details.",
										},
										"total_price": map[string]interface{}{
											"type":        []string{"number", "null"},
											"description": "The total price for this line item, typically calculated as quantity multiplied by unit price, before taxes or discounts.",
										},
									},
									"additionalProperties": false,
								},
								"description": "The individual products or services purchased in this transaction. Each item typically includes a description, quantity, unit price, and total price. Formats vary widely, from tables to lists or other structures.",
							},
							"tax_amount": map[string]interface{}{
								"type": "object",
								"required": []string{"amount", "iso_4217_currency_code"},
								"properties": map[string]interface{}{
									"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
									"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
								},
								"description":          "The total tax charged for this transaction. May be labeled as 'Tax', 'Sales Tax', 'VAT', or similar. If multiple taxes are present, this should be the combined total.",
								"extend:type":          "currency",
								"additionalProperties": false,
							},
							"vendor_name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The name of the business, merchant, or entity that issued this receipt and received payment. This is the party providing goods or services.",
							},
							"total_amount": map[string]interface{}{
								"type": "object",
								"required": []string{"amount", "iso_4217_currency_code"},
								"properties": map[string]interface{}{
									"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
									"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
								},
								"description":          "The total amount paid for this transaction, including all items, taxes, fees, and adjustments. This is the final payment amount and may be labeled as 'Total', 'Amount Paid', or similar.",
								"extend:type":          "currency",
								"additionalProperties": false,
							},
							"vendor_email": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The email address of the business or merchant issuing the receipt, if present.",
							},
							"vendor_phone": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The phone number of the business or merchant issuing the receipt. May include country and area codes.",
							},
							"change_amount": map[string]interface{}{
								"type": "object",
								"required": []string{"amount", "iso_4217_currency_code"},
								"properties": map[string]interface{}{
									"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
									"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
								},
								"description":          "The amount of change returned to the customer, if payment exceeded the total amount. May be labeled as 'Change', 'Cash Back', or similar.",
								"extend:type":          "currency",
								"additionalProperties": false,
							},
							"customer_name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The name of the customer or purchaser, if specified on the receipt. May be labeled as 'Customer', 'Sold To', or similar.",
							},
							"payment_method": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The method of payment used for this transaction, such as 'Credit Card', 'Cash', 'Debit', 'Mobile Payment', or specific card type. May include partial card numbers or other identifiers.",
							},
							"receipt_number": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The unique identifier or reference number for this receipt. This may include numbers, letters, or special characters. Commonly labeled as 'Receipt #', 'Transaction ID', or similar, but the key is identifying the primary reference for this transaction.",
							},
							"vendor_address": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The address of the business or merchant issuing the receipt. May include street, city, state, postal code, and country. Sometimes appears as a block of text or in multiple lines.",
							},
							"subtotal_amount": map[string]interface{}{
								"type": "object",
								"required": []string{"amount", "iso_4217_currency_code"},
								"properties": map[string]interface{}{
									"amount":                   map[string]interface{}{"type": []string{"number", "null"}},
									"iso_4217_currency_code":   map[string]interface{}{"type": []string{"string", "null"}},
								},
								"description":          "The sum of all item prices before taxes, discounts, or additional fees. May be labeled as 'Subtotal', 'Items Total', or similar.",
								"extend:type":          "currency",
								"additionalProperties": false,
							},
							"transaction_date": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The date when the transaction occurred and the receipt was issued. This is the official date for accounting and record-keeping purposes. May be labeled as 'Date', 'Transaction Date', or similar.",
								"extend:type": "date",
							},
						},
						"additionalProperties": false,
					},
					"baseProcessor": "extraction_light",
					"advancedOptions": map[string]interface{}{
						"reviewAgent": map[string]interface{}{
							"enabled": false,
						},
						"advancedMultimodalEnabled": false,
					},
				},
			},
		},
	},
}

func main() {
	fmt.Printf("Deploying \"%s\"…\n", workflow["name"])

	if state.WorkflowID != "" {
		fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", state.WorkflowID)
		_, _ = apiCall("POST", fmt.Sprintf("/workflows/%s", state.WorkflowID), map[string]interface{}{"steps": workflow["steps"]})
	} else {
		found := false
		list, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", url.QueryEscape(workflow["name"].(string))), nil)
		if err == nil {
			var items []map[string]interface{}
			if data, ok := list["data"].([]interface{}); ok {
				for _, item := range data {
					items = append(items, item.(map[string]interface{}))
				}
			} else if data, ok := list["items"].([]interface{}); ok {
				for _, item := range data {
					items = append(items, item.(map[string]interface{}))
				}
			}

			for _, item := range items {
				if name, ok := item["name"].(string); ok && name == workflow["name"].(string) {
					if id, ok := item["id"].(string); ok {
						state.WorkflowID = id
						_ = saveState()
						fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflow["name"], id)
						_, _ = apiCall("POST", fmt.Sprintf("/workflows/%s", id), map[string]interface{}{"steps": workflow["steps"]})
						found = true
						break
					}
				}
			}
		}

		if !found {
			created, err := apiCall("POST", "/workflows", workflow)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error: %v\n", err)
				os.Exit(1)
			}

			var wfID string
			if id, ok := created["id"].(string); ok {
				wfID = id
			} else if wf, ok := created["workflow"].(map[string]interface{}); ok {
				if id, ok := wf["id"].(string); ok {
					wfID = id
				}
			}

			if wfID == "" {
				fmt.Fprintf(os.Stderr, "Error: Could not read created workflow id from response\n")
				os.Exit(1)
			}

			state.WorkflowID = wfID
			_ = saveState()
			fmt.Printf("+ created workflow (%s)\n", wfID)
		}
	}

	_, _ = apiCall("POST", fmt.Sprintf("/workflows/%s/versions", state.WorkflowID), map[string]interface{}{})

	fmt.Println("\nDone. Run documents through it with:")
	fmt.Printf("  POST %s/workflow_runs  { workflow: { id: \"%s\" }, file: { url: \"https://…\" } }\n", API, state.WorkflowID)
	fmt.Println("Or open the workflow in the Extend dashboard to review and deploy it.")
}

import (
	"net/http"
	"net/url"
)

Frequently Asked Questions (FAQ)

Define `line_items` as an array of objects in your extraction schema with `description`, `quantity`, and `unit_price` fields; the extractor will automatically detect and populate all rows regardless of count. Use clear field descriptions like "itemized purchase (e.g., 'Milk 2L')"; the description matters more than the field name for accuracy.
For financial reconciliation, require `confidence >= 0.85` on critical fields like `total_amount` and `tax`; below that, flag for human review. Test against 10–20 representative receipts in your domain first—confidence varies by receipt quality and font clarity.
Use `parseRuns.createAndPoll()` (async) for production; sync calls timeout on receipts >10 pages. Async is only marginally slower (2–5 sec per receipt) and handles edge cases transparently.
Tags
Receipt ProcessingTax ExtractionRetail SalesGST Calculation
About this template

This template processes point-of-sale tax invoices and cash receipts from retail businesses. It captures merchant information, line-item details with quantities and prices, payment amounts, and change due.

Document formats
  • PDF
  • Images & Scans
Requirements
  • Scanned documents
  • Long tables

Relevant templates for Financial & Banking

  1. 01
    Driver's License ExtractorParse → Extract
    Extracts personal identification and licensing data from driver license documents.
    PDFImages & Scanswww.extend.ai/templates/driver-license-template
  2. 02
    Bank Statement ExtractorParse → Extract
    Extracts account summaries, balances, deposits, NSF flags, and transaction details from bank statements.
    PDFImages & Scanswww.extend.ai/templates/bank-statement
  3. 03
    Vendor Invoice ExtractorParse → Extract
    Extracts charges, billing details, line items, totals, and due dates vendor invoices.
    PDFImages & Scanswww.extend.ai/templates/vendor-invoice
  4. 04
    Pay Stub ExtractorParse → Extract
    Extracts employee earnings, deductions, taxes, and net pay from pay stubs.
    PDFwww.extend.ai/templates/pay-stub
  5. 05
    Check ExtractorParse → Extract
    Extracts check details including payee, amount, date, and bank routing information.
    PDFImages & Scanswww.extend.ai/templates/check
  6. 06
    Wire Transfer Instructions ExtractorParse → Extract
    Extracts wire transfer procedures, contact info, and operational hours from banking guides.
    PDFWord / DOCXwww.extend.ai/templates/wire-transfer-instructions
  7. 07
    Onboarding Package ExtractorParse → Extract
    Extracts account summaries, transaction details, and balances from bank statements.
    PDFImages & Scanswww.extend.ai/templates/personal-bank-statement
  8. 08
    1040 Tax Return ExtractorParse → Extract
    Extracts personal and dependent information from U.S. individual income tax returns.
    PDFImages & Scanswww.extend.ai/templates/tax-return-form-1040