Hand a modern multimodal model a scanned invoice and ask for the supplier, the total and the line items as JSON, and it will usually get them right. That is a real change from a few years ago, when the same job needed an OCR engine, a layout model, templates per supplier and a lot of regular expressions.
“Usually” is the problem. When a model gets a document wrong, it rarely says so. It returns a well-formed JSON object with a plausible invoice number that is not on the page, or two columns swapped, or twelve of fifteen line items and no sign that three were dropped. In accounts payable, insurance or lending, that error goes straight into a system of record.
This guide covers what makes documents hard, how classic OCR and document-processing services compare with multimodal LLMs as of September 2026, and the hybrid pipeline we build when the extracted data has to be right: schemas, validation, confidence routing, business-rule cross-checks, evaluation and cost.
What makes documents hard
“Document AI” covers very different problems. A clean, digitally generated PDF invoice is close to solved. A faxed, stamped, partly handwritten claim form is not. Before choosing an approach, sort your documents by the traits below, because each one breaks extraction in a different way. Public benchmarks such as OmniDocBench now test parsers across many document sources and layout types, which is useful context, but they cannot tell you how a tool will do on your suppliers' invoices.1
| Trait | Why it breaks extraction | What helps |
|---|---|---|
| Variable layouts | Thousands of suppliers, each with their own invoice design; templates do not scale | Pretrained invoice models or LLMs that read by meaning, not position |
| Tables | Merged cells, multi-line descriptions, tables split across pages, missing column headers | Dedicated table extraction, row counts, arithmetic checks |
| Handwriting | Low recognition accuracy, especially for numbers, names and non-English scripts | OCR with handwriting support and per-word confidence, human review |
| Stamps, signatures, checkboxes | Overlap with printed text; meaning depends on presence, not content | Selection-mark and signature detection, explicit yes/no fields |
| Multi-page documents | Totals on the last page, headers repeated, context limits, output limits | Page-aware processing, splitting, completeness checks |
| Scans and photos | Skew, blur, low resolution, shadows, small fonts lost when images are downscaled | Image clean-up, minimum resolution checks, rejecting unreadable pages |
| Mixed bundles | One PDF holding an invoice, a delivery note and a remittance advice | Classification and splitting before extraction |
Three approaches
Classic OCR and intelligent document processing services
The three big clouds each sell a document service that combines OCR, layout analysis and pretrained extractors for common document types. Their strengths are predictable: word-level bounding boxes, per-field confidence scores, stable per-page pricing, and no tendency to invent text that is not there. Their weakness is flexibility. A field the pretrained model does not know about needs a custom model, queries, or code.
- Amazon Textract detects printed and handwritten text, forms, tables, layout, signatures and answers to natural-language Queries.
AnalyzeExpenseis built for invoices and receipts and normalises field names across documents. Each item comes with a confidence score from 0 to 100 and bounding box coordinates. Handwriting, invoices and receipts, and Queries are English-only.2 - Google Cloud Document AI offers Enterprise Document OCR (200+ languages, including handwriting), Form Parser, Layout Parser, a pretrained Invoice Parser, and Custom Extractor, several versions of which are powered by Gemini models. Generative extraction in Custom Extractor officially supports English only.3
- Azure Document Intelligence, now branded Azure Document Intelligence in Foundry Tools, has Read and Layout models, prebuilt models such as
prebuilt-invoiceandprebuilt-receiptwith Microsoft-maintained field schemas, and custom models.4 The Layout model can return Markdown with tables as HTML, which is a convenient input for an LLM, and returns per-word confidence. It processes PDFs of up to 2,000 pages.5
Multimodal LLMs reading the page directly
The major model APIs accept PDFs directly and look at each page as an image, usually alongside any text embedded in the file. Claude converts each page to an image and supplies the extracted text with it; text alone typically costs 1,500 to 3,000 tokens per page, plus image tokens. A request can include up to 600 pages, or 100 when the context window is under 1M tokens, and dense PDFs can fill the context before reaching that limit.6 Gemini counts each page as 258 tokens, accepts files up to 50 MB or 1,000 pages, and scales large pages down to 3072 by 3072 pixels.7 OpenAI's API sends both extracted text and page images to vision-capable models, with a 50 MB limit per request.8
The strength is reading by meaning. An LLM handles a new supplier layout, a table without headers, or a field described in plain English (“the date the goods were delivered, not the invoice date”) without any training. The weaknesses are the ones in the introduction: no reliable per-field confidence, occasional invented values, and errors that look exactly like correct answers.
Hybrids
In a hybrid, an OCR or layout service produces text with coordinates and confidence, and an LLM does the interpretation, seeing both the OCR output and the page image. The OCR layer gives you something to check the LLM against. If the model returns an invoice number, you can confirm that string appears in the OCR text and know which page and position it came from.
| OCR / IDP service | Multimodal LLM | Hybrid | |
|---|---|---|---|
| New layouts | Pretrained types only; custom models for the rest | Strong | Strong |
| Custom fields | Queries or custom training | Describe them in the schema | Describe them in the schema |
| Confidence signal | Per word and per field | Weak; must be built | OCR confidence plus cross-checks |
| Invented values | Rare | Possible | Caught by evidence checks |
| Provenance (page, position) | Bounding boxes | Only if asked, unverified | Bounding boxes via OCR match |
| Cost and latency | Predictable per page | Depends on model and tokens | Sum of both |
Our default for documents that feed a financial or regulated system is the hybrid. For high-volume, low-stakes reading, such as routing inbound mail or summarising contracts for search, an LLM on its own is often enough. For a single, stable form type at very high volume, a pretrained or custom IDP model can still be the cheapest and most predictable choice.
The pipeline we build
- Ingest and check quality. Reject or flag pages below a minimum resolution, password-protected files and empty pages before spending money on them.
- Classify and split. Separate invoices from credit notes, delivery notes and statements. Most extraction errors in bundles start as classification errors.
- OCR and layout. Get text, tables, coordinates and confidence from an IDP service.
- Extract to a schema. An LLM reads the layout output and the page images and fills a strict JSON schema, including an evidence quote and page number for key fields.
- Validate. Types, formats, arithmetic and completeness, in code.
- Cross-check. Against the vendor master, open purchase orders, goods receipts and previous invoices.
- Route. Straight through, or to a human review queue with the reason attached.
- Post and learn. Write to the ERP as a draft or for approval. Every human correction becomes a labelled example for the evaluation set.
Structured output, then validate anyway
Every major API now supports schema-constrained output. OpenAI Structured Outputs guarantees that responses follow your JSON Schema, and its Python SDK accepts a Pydantic model directly.9 Claude's structured outputs do the same, with a parse() helper for Pydantic models.10
A schema guarantees shape, not truth. Both providers document the cases where you do not even get the shape: a response cut off at the output token limit may be incomplete JSON, and a refusal may not match the schema.910 Claude's implementation also rejects some JSON Schema features, such as numeric minimums and string lengths, which its SDK moves into descriptions and checks locally.10 So we keep the business validation in our own Pydantic model and run it regardless of provider:
from datetime import date
from decimal import Decimal
from pydantic import BaseModel, Field, model_validator
class FieldEvidence(BaseModel):
field: str # e.g. "invoice_number"
page: int
quote: str # exact text copied from the page
class LineItem(BaseModel):
description: str
quantity: Decimal
unit_price: Decimal
amount: Decimal
class Invoice(BaseModel):
supplier_name: str
supplier_tax_id: str | None
invoice_number: str
invoice_date: date
currency: str = Field(description="ISO 4217 code, e.g. EUR")
po_number: str | None = Field(description="null if no PO number is printed")
subtotal: Decimal
tax_total: Decimal
total: Decimal
line_items: list[LineItem]
printed_line_count: int = Field(description="number of line rows printed on the document")
evidence: list[FieldEvidence]
@model_validator(mode="after")
def check_invoice(self):
problems = []
cent = Decimal("0.01")
if len(self.currency) != 3 or not self.currency.isupper():
problems.append("currency is not an ISO code")
if len(self.line_items) != self.printed_line_count:
problems.append("line items missing or extra") # silent truncation
for i, li in enumerate(self.line_items):
if abs(li.quantity * li.unit_price - li.amount) > cent:
problems.append(f"line {i + 1}: qty x price != amount") # swapped columns
if abs(sum(li.amount for li in self.line_items) - self.subtotal) > cent:
problems.append("lines do not sum to subtotal")
if abs(self.subtotal + self.tax_total - self.total) > cent:
problems.append("subtotal + tax != total")
if problems:
raise ValueError("; ".join(problems))
return selfTwo more checks run after parsing. First, every evidence quote must be found in the OCR text of the stated page, after normalising whitespace and case. A value with no matching evidence is treated as possibly invented. Second, the response's stop reason must be a normal finish, not a token limit. A cut-off response that happens to parse is still incomplete.
import re
def norm(s: str) -> str:
return re.sub(r"\s+", " ", s).strip().lower()
def unsupported_fields(inv: Invoice, ocr_pages: dict[int, str]) -> list[str]:
missing = []
for ev in inv.evidence:
page_text = norm(ocr_pages.get(ev.page, ""))
if norm(ev.quote) not in page_text:
missing.append(ev.field)
return missingTwo schema habits help as much as validation. Make absence explicit: a nullable po_number with “null if not printed” in the description gets far fewer invented values than a required string. And ask for counts the model can see, like printed_line_count, so you can detect when the list it returned is shorter than the document.
Confidence and human review queues
IDP services return confidence scores. Textract, for example, gives a 0 to 100 score per item and suggests rules such as flagging anything below a threshold.2 LLMs do not give you a calibrated per-field number. Asking the model to rate its own confidence produces a field that looks useful and is not. So we build confidence from signals we can check:
| Signal | Route to review when |
|---|---|
| Validation result | Any arithmetic, format or completeness check fails |
| Evidence match | A key field has no matching quote in the OCR text |
| OCR confidence | Words behind a key field fall below your threshold (common with handwriting) |
| Extractor agreement | The LLM and the IDP prebuilt model disagree on total, date or invoice number |
| Business rules | No PO match, price variance over tolerance, possible duplicate |
| Risk rules | New supplier, changed bank details, amount above an approval limit |
The review screen matters as much as the routing. Show the page image with the source of each field highlighted, the extracted value, and the specific reason the document was routed. Reviewers who see “lines do not sum to subtotal” fix the problem in seconds. Reviewers who see a full form and no reason re-key the whole document, and the automation gains disappear.
Cross-checks against systems of record
The strongest check on an extracted invoice is not another model. It is the data you already hold. In accounts payable, these checks catch both extraction errors and real-world problems:
- Supplier match. Resolve the supplier against the vendor master by tax ID first, then name. An unknown supplier is never posted automatically.
- PO matching. Two-way match (invoice against PO price and quantity) or three-way match (plus goods receipt), with tolerances agreed with finance.
- Totals and tax. Recompute tax from the lines and the supplier's expected rate, not just the printed figure.
- Duplicates. Same supplier, invoice number and amount, and also near-duplicates: the same amount and date with an invoice number that differs only by punctuation or a leading zero.
- Bank details. If the bank account on the invoice differs from the vendor master, it always goes to a human and is verified out of band. This is a fraud control as much as a data quality one.
Evaluating extraction
Measure at field level and at document level, because they tell different stories. Here is a worked example with labelled assumptions, not client data.
Assumptions: 200 labelled invoices, 12 header fields each, so 2,400 field slots. In the ground truth, 2,300 fields are present and 100 are legitimately absent (no PO number printed, for example). The pipeline returns 2,290 values:
| Outcome | Count |
|---|---|
| Correct value (after normalising dates and amounts) | 2,231 |
| Wrong value for a field that is present | 44 |
| Value returned for a field that is absent (invented) | 15 |
| Present field left empty | 25 |
- Precision = correct / returned = 2,231 / 2,290 = 97.4%.
- Recall = correct / present = 2,231 / 2,300 = 97.0%.
That sounds excellent. Now look at documents. Suppose the 84 field errors fall across 52 invoices, so 148 of 200 are fully correct (74%). The routing rules send 61 invoices to review, and 48 of them contain an error. The other 139 go straight through, and 4 of those still contain an error.
- Straight-through rate = 139 / 200 = 69.5%.
- Escaped error rate = 4 / 139 = 2.9% of auto-posted invoices.
- Review precision = 48 / 61 = 79% of reviewed invoices actually needed a fix.
Field accuracy of 97% turned into 70% automation with 4 bad postings per 139. Those are the numbers finance cares about. Track them per field (totals and bank details matter more than a supplier's phone number), per supplier and per document type, and look hard at the escaped errors: each one is a missing check. Invented values deserve their own line in the report, because they are the errors reviewers are least likely to catch.
Cost per page
List prices as of September 2026. The AWS examples use US West (Oregon); Azure figures are pay-as-you-go for East US. All are for the first volume tier.
| Option | Price | Per 1,000 pages |
|---|---|---|
| Textract Detect Document Text (OCR) | $0.0015 / page | $1.5011 |
| Textract Analyze Expense (invoices, receipts) | $0.01 / page | $1011 |
| Textract Analyze Document (forms + tables + queries) | $0.07 / page | $7011 |
| Google Enterprise Document OCR | $1.50 / 1,000 pages | $1.5012 |
| Google Invoice Parser | $0.10 per 10 pages | $1012 |
| Google Form Parser or Custom Extractor | $30 / 1,000 pages | $3012 |
| Azure Read | $1.50 / 1,000 pages | $1.5013 |
| Azure prebuilt models (layout, invoice, receipt) | $10 / 1,000 pages | $1013 |
| Azure custom extraction (incl. generative) | $30 / 1,000 pages | $3013 |
LLM cost depends on tokens, so it needs assumptions. We assume 3,000 input tokens per page (page image, extracted text, instructions and schema) and 500 output tokens. Token counts per page vary a lot by provider and content, as the figures above show, so measure yours with the provider's token counter.
- Low-cost tier, at $0.30 per million input tokens and $2.50 per million output (Gemini 2.5 Flash's list price):14 3,000 × $0.30/M + 500 × $2.50/M = $0.0009 + $0.00125 = about $2.15 per 1,000 pages.
- Mid tier, at $3 and $15 per million (Claude Sonnet 4.5's list price):15 $0.009 + $0.0075 = about $16.50 per 1,000 pages.
Both providers offer batch APIs at roughly half price, which suits document work that does not need an answer in seconds.1514
Now a monthly example. Assume 10,000 invoices of two pages each (20,000 pages), and a fully loaded reviewer cost of $40 an hour at three minutes per reviewed invoice, or $2 per review:
| Pipeline | Extraction cost | Review at 30% | Review at 15% |
|---|---|---|---|
| Textract Analyze Expense only | 20,000 × $0.01 = $200 | $6,000 | $3,000 |
| Low-cost LLM only | 20,000 × $0.00215 = $43 | $6,000 | $3,000 |
| Hybrid: OCR + low-cost LLM, mid-tier second pass on 20% of pages | $30 + $43 + (4,000 × $0.0165 = $66) = $139 | $6,000 | $3,000 |
The extraction line is noise next to the review line. Halving the review rate saves $3,000 a month in this example; switching to the cheapest extractor saves at most $157. Spend on whatever lowers the review rate without raising escaped errors: better checks, a second extractor on hard documents, cleaner review tooling.
Privacy and data residency
Invoices, claims and contracts contain personal and commercial data, so check each service's data terms before the pilot, not after:
- Training and improvement. Amazon Textract may store and use inputs to improve AWS machine-learning services unless you opt out with an AWS Organizations AI services opt-out policy.2 Google does not use prompts or responses from paid Gemini API services to improve its products, but unpaid use is different: Google may use that data, human reviewers may read it, and Google says not to submit sensitive or personal information.16 Make sure nobody prototypes with real documents on a free tier.
- Retention. Azure Document Intelligence processes data in the resource's region and keeps inputs and results for 24 hours after an analysis completes, unless you delete them earlier through the API.17
- Region. Pin processing to the region your data rules require, for both the IDP service and the model endpoint. Generative features are often available in fewer regions than classic OCR; Google lists a short set of regions for generative Custom Extractor, for example.3
- Your own logs. The pipeline's traces, review queue and evaluation set hold the same sensitive data. Give them the same retention and access rules as the source documents.
Failure modes
| Failure | What it looks like | Control |
|---|---|---|
| Invented fields | A PO number, tax ID or due date that is not on the page, in a perfectly valid format | Nullable fields, evidence quotes matched against OCR text |
| Swapped columns | Unit price and amount exchanged, or quantity read from the wrong column | Per-line arithmetic, sum-to-subtotal check |
| Silent truncation | Line items stop partway through a long table; the JSON still parses | Printed line count, stop-reason check, page-by-page extraction for long documents |
| Lost detail on large pages | Small print misread because the page image was downscaled | Use OCR text alongside the image; crop or tile dense pages |
| Wrong document type | A credit note posted as an invoice, or a statement read as one large invoice | Classification step, sign checks on totals |
| Locale errors | 1.234,56 read as 1.23456; 03/04 read as March instead of April | Supplier-country defaults, cross-check against PO amounts |
| Instructions in the document | Text in a PDF that tells the model to approve the invoice or change a field | The extractor has no tools and returns only schema fields; approval rules live in code |
Checklist
- Sort your documents by the traits that make them hard, and sample each group for evaluation.
- Label at least a couple of hundred real documents, including the ugly ones, before choosing a vendor.
- Pick the approach per document group: IDP service, LLM, or hybrid.
- Use schema-constrained output, with nullable fields for anything that may be absent.
- Validate types, arithmetic, completeness and stop reason in your own code.
- Require evidence quotes for key fields and match them against OCR text.
- Cross-check against the vendor master, purchase orders, receipts and past invoices.
- Route to review with a specific reason, and show reviewers the source on the page.
- Report precision, recall, straight-through rate and escaped error rate, per field and per supplier.
- Budget for review time, not just per-page prices.
- Confirm data use, retention and region for every service before real documents go in.
- Turn every reviewer correction into a new evaluation example.
If you want this pipeline running on your AP inbox without building it yourself, our Documents & AP agent is set up in your own tools and run for you.
Sources
- Ouyang et al., “OmniDocBench: Benchmarking Diverse PDF Document Parsing with Comprehensive Annotations,” CVPR 2025, arXiv:2412.07626 — arxiv.org/abs/2412.07626
- AWS, Amazon Textract FAQs — aws.amazon.com/textract/faqs/
- Google Cloud, Document AI documentation — “Processor list” — docs.cloud.google.com/document-ai/docs/processors-list
- Microsoft Learn, “What is Azure Document Intelligence in Foundry Tools?” — learn.microsoft.com/en-us/azure/ai-services/document-intelligence/overview
- Microsoft Learn, “Document layout analysis” (Document Intelligence layout model) — learn.microsoft.com/en-us/azure/ai-services/document-intelligence/prebuilt/layout
- Anthropic, Claude API documentation, “PDF support” — platform.claude.com/docs/en/build-with-claude/pdf-support
- Google, Gemini API documentation, “Document understanding” — ai.google.dev/gemini-api/docs/document-processing
- OpenAI API documentation, “File inputs” (PDF files) — developers.openai.com/api/docs/guides/pdf-files
- OpenAI API documentation, “Structured model outputs” — developers.openai.com/api/docs/guides/structured-outputs
- Anthropic, Claude API documentation, “Structured outputs” — platform.claude.com/docs/en/build-with-claude/structured-outputs
- AWS, Amazon Textract pricing — aws.amazon.com/textract/pricing/
- Google Cloud, Document AI pricing — cloud.google.com/document-ai/pricing
- Microsoft Azure, Azure Document Intelligence pricing (pay-as-you-go S0 rates for East US, read from the Azure Retail Prices API) — azure.microsoft.com/en-us/pricing/details/ai-document-intelligence/
- Google, Gemini Developer API pricing — ai.google.dev/gemini-api/docs/pricing
- Anthropic, Claude API documentation, “Pricing” — platform.claude.com/docs/en/about-claude/pricing
- Google, Gemini API Additional Terms of Service — ai.google.dev/gemini-api/terms
- Microsoft Learn, “Data, privacy, and security for Document Intelligence” — learn.microsoft.com/en-us/azure/foundry/responsible-ai/document-intelligence/data-privacy-security
Prices, limits and product names come from each vendor's documentation and price list. They change often, so confirm against the linked page before budgeting.
