Document Extractor
Turns an invoice, packing list, or bill of lading into structured, reviewable line items.
Single-purpose LLM agent (vision). Turns a PDF or image of a customs document into structured fields, ready for downstream agents.
What it does
Reads a commercial invoice (PDF or image) and returns a Zod-validated structure: invoice number and date, supplier (name + country), buyer, currency and total, PO/reference numbers, vessel/voyage and bill-of-lading number if printed, per-line items (description, SKU, declared HTS as printed, quantity, unit of measure, unit price, line total), and free-text broker notes. This is the input every other agent depends on — if extraction fails, nothing downstream runs. (Bill-of-lading extraction is a separate extractor; non-invoice inputs are flagged, see below.)
Inputs
{
filename: string; // "INV-22041.pdf"
mimeType: string; // "application/pdf" | "image/png" | …
bytes: Buffer; // raw file
source?: 'upload' | 'inbox' | 'po-portal';
}
Extraction is cost-tiered, text-first. For a digital PDF with a real text layer, the text is pulled from the PDF and extracted cheaply on Claude Haiku (the default path). If that result fails a completeness gate — or the input is a scan, an image, or a thin-text PDF — it escalates to Claude Sonnet reading the layout-aware vision block (document/image content, no separate OCR), which is accurate for tables and line items. Same schema and prompt on either tier; only the model and input differ, and the tier that fired is reported in the result meta.
Outputs
{
invoice_number: 'INV-22041', // required
invoice_date: '2026-05-24', // ISO 8601, or null
supplier_name: 'Hangzhou Lanyu Textile Co., Ltd.', // or null
supplier_country: 'CN', // or null
buyer_name: 'Northwell Outfitters',// importer / bill-to, or null
currency: 'USD', // ISO 4217, or null
total: 12_440.00, // document currency, decimals, or null
references: ['PO-88231'], // PO / contract numbers
vessel: 'ONE TRIUMPH / 071E', // carrier vessel + voyage if printed, or null
bill_of_lading: 'ONEYSHA1234567', // master/house B/L if printed, or null
lines: [
{
line_number: 1,
description: "Men's 100% cotton crew-neck T-shirts, knit, size M",
sku: 'LY-CT-M', // or null
declared_hts: '6109.10', // VERBATIM as printed, or null — never inferred
quantity: 12000,
unit_of_measure: 'EA',
unit_price: 1.00, // document currency, decimals, or null
line_total: 12_000.00,
},
// … more lines
],
notes: ['Incoterms: FOB Ningbo', 'Country of origin: CN'], // free-text broker flags
}
The structure is flat: amounts are in document-currency decimals (not cents), and Incoterms, ports/origin, mode, and country-of-origin notes are surfaced as free-text notes rather than typed fields — the customs-clearance module derives ports, consignee, and per-line country-of-origin downstream when it builds the draft shipment.
Schema validation. Most fields are nullable by design: the model leaves what it can't read as null rather than inventing a value to satisfy the structure. A few identifying fields are required and non-null — invoice_number, and each line's description, quantity, unit_of_measure, and line_total. If the document isn't a commercial invoice at all, the model returns invoice_number: 'NOT_AN_INVOICE' and explains in notes[0] what it actually saw, rather than forcing a wrong parse. Every extracted field is broker-reviewable in the drawer, and missing fields are flagged by downstream services so the broker knows what to ask the supplier for.
When it runs
- Drag-drop upload on Dashboard / Simulation / New entry wizard (
uploadsource) → fires immediately, surfaces the structured result in the inbox row. - Email inbox parse — when a packet arrives, every attachment runs through the extractor on receipt.
- Re-extract on demand — broker clicks "Re-parse" in the drawer when the source PDF is updated.
Dependencies
- Two models:
Claude Haikuover the extracted PDF text (default, cheap tier) and a vision-capableClaude Sonnetthat accepts the PDF or image directly (escalation tier for scans/images/thin-text). - PDF text extraction for the text-first tier; no third-party OCR — the vision tier reads the layout natively when text isn't available.
- No retrieval, no DB lookups at extract time. Document-to-structure only. The HTS hint surfaced from the document is only a hint; the classifier re-runs from the description regardless.
What success looks like
High-accuracy structured extraction across the fields a broker needs to open an entry — document type, parties, ports, dates, line items with quantities and values, and totals — from real commercial invoices, packing lists, bills of lading, and POs. Every field is surfaced for review rather than trusted blindly: we do not auto-file based on extracted data alone. The broker reviews every field in the drawer before transmit.
What it does NOT do
- Does not invent values. Unreadable fields come back
null(most fields are nullable; only a few identifying fields are required), and a non-invoice returns theNOT_AN_INVOICEsentinel instead of a forced parse. The drawer shows missing fields to the broker as "Missing — broker to confirm". - Does not classify HTS codes. It surfaces any HS hint printed on the document, but the HTS Classifier re-runs from the description for every line regardless.
- Does not translate documents. Foreign-language invoices are extracted in the original language; translation is a separate concern.
- Does not read handwriting reliably. Scanned and printed text only. Handwritten amendments are flagged for broker review.
- Does not stitch multi-document packets (invoice + packing list + BoL) into a single record. Each document is extracted independently; the customs-clearance module reconciles them at the entry level.
Roadmap
- Q3 2026 — Multi-document packet reconciliation: when invoice + packing list + BoL arrive together, merge into one entry draft so the broker isn't reconciling line numbers manually.
- Q3 2026 — Handle handwritten amendments by surfacing the raw region to the broker instead of guessing.
- Q4 2026 — Auto-detect when a document is a revised version of one already in the inbox (same supplier + invoice number, different totals) and prompt the broker to diff.
- 2027 — Language hints: detect non-English documents and surface a "translate" affordance in the drawer for the broker.
Put your own book of entries through Aduaria.