How I built the Invoice Processing demo
Pull structured data — vendor, line items, totals, due date — out of a PDF, Word doc, or plain text invoice with a real text layer. Not a formatted sample PDF; whatever gets forwarded to AP.
Last reviewed
The problem
Invoices arrive as PDFs, scanned images saved as PDFs, Word docs, or plain email text pasted into a form. Most invoice-AI demos quietly assume the first case. Real AP inboxes get all of them, plus the occasional file that isn't actually an invoice at all. This demo currently covers PDFs, DOCX, and plain text with a real text layer — a scanned image with no embedded text (a photo, or a PDF that's really just a picture) needs an OCR step this pipeline doesn't run yet, and isn't demonstrated here.
The pipeline
File upload hits a shared /api/extract-text route before the invoice-specific logic ever runs:
- 01Detect file type by extension, not MIME type — browsers report MIME types inconsistently across OS/browser combinations, extensions don't.
- 02PDF → unpdf (serverless-compatible; pdf-parse and pdfjs-dist both hit Node API assumptions that break under Vercel's runtime — this took two failed attempts to learn).
- 03DOCX → mammoth, extracting raw text only.
- 04TXT/MD/CSV → read directly, no parsing needed.
- 05Text truncated to a 10,000-character ceiling before it ever reaches a model call.
The extraction call
The prompt asks for one JSON object — vendor, invoice number, dates, PO number, line items, subtotal, tax, total, payment instructions, and a self-reported confidence score — and nothing else. The response is regex-matched for the outermost{...}block before JSON.parse, since models occasionally wrap output in a sentence even when told not to.
Why Haiku, not Sonnet
This is field extraction against text that's already been parsed out of the source file — there's no multi-step reasoning, no judgment call, no ambiguity to weigh. Claude Haiku 4.5 handles it at a fraction of the latency and cost of a larger model. The Fusion Cloud AI-Readiness Assessment demo, which does require judgment, runs on Sonnet 5 instead — matching model size to the actual reasoning load is a cost decision I apply to client work, not just this site.
Labeled benchmark
Run 2026-08-02Model
Claude Haiku 4.5 (claude-haiku-4-5-20251001)
Sample size
8 synthetic invoices, 21 scored fields
Data source
8 hand-authored synthetic invoices (clean, messy, multilingual, non-invoice, credit-memo formats) run live against the production endpoint at tioga.ai — not a cached or pre-recorded result.
Field-match rate (vendor / invoice # / total)
21/21 fields (100%)
Successful extractions
8/8 (HTTP 200)
Average latency
~1.7s
Non-invoice input
Correctly flagged, confidence 5/100, no fabricated fields
Limitations
- —Sample size is small (8 cases) and hand-authored, not drawn from a real-world invoice corpus — a production engagement would validate against your actual invoice formats before go-live.
- —Field-match scoring used fuzzy substring matching, not strict equality — an exact-match check would likely show a lower, more conservative number.
- —Only 3 fields (vendor, invoice number, total) were scored; line-item and date-field accuracy weren't measured in this run.