Vision LLM invoice extraction in Node.js works by sending invoice images or PDFs directly to a multimodal AI model (GPT-4o, Claude) that reads the visual layout and returns structured data, skipping traditional OCR pipelines entirely. The simplest implementation path is Zerox, an open-source library that wraps vision model calls into a single function and returns structured text from any document. When you need full control over output shape and validation, you can call the OpenAI or Anthropic APIs directly, passing JSON schema constraints (enforced with Zod in TypeScript) to get type-safe invoice fields back from the model.
This shift toward vision-model-based extraction is not speculative. According to GitHub's 2025 Octoverse report, more than 1.1 million public repositories now use an LLM SDK, with 693,867 of those projects created in just the past 12 months — a 178% year-over-year increase. For Node.js developers building document processing features, vision LLMs have become the default starting point because they eliminate the brittle template-matching and coordinate-parsing logic that traditional OCR demands.
Extracting Invoice Data with Zerox
Zerox is an open-source Node.js library that handles the messiest parts of vision LLM document extraction for you. Under the hood, it converts each page of a PDF (or image) into an image, sends those images to a vision model of your choice, and returns the extracted content. It supports OpenAI, Anthropic (Claude), Google (Gemini), and AWS Bedrock as providers, so you're not locked into a single vendor.
For invoice extraction specifically, Zerox solves three problems at once: PDF-to-image conversion, prompt construction, and vision API orchestration. You don't need to install Poppler, write system prompts, or manage base64 encoding. A few lines of code get you from a PDF file to extracted data.
Basic text extraction
Install the package and set your API key:
npm install zerox
import { zerox } from "zerox";
const result = await zerox({
filePath: "./invoices/acme-inv-2024-0042.pdf",
openaiAPIKey: process.env.OPENAI_API_KEY,
model: "gpt-4o",
pagesToExtractText: "all",
});
for (const page of result.pages) {
console.log(page.content);
}
This returns the raw text content from each page of the invoice. Useful for previewing what the model sees, but for most production use cases, you need structured fields, not a wall of text.
Getting structured JSON output
The real value of Zerox for invoice processing is its schema-based extraction. Instead of parsing raw text with regex, you define the shape of the data you want and let the vision model extract it directly. This is the pattern that turns Zerox from a text extraction tool into an invoice data pipeline.
Define your invoice schema as a JSON schema object, then pass it via the outputSchema parameter:
import { zerox } from "zerox";
const invoiceSchema = {
type: "object",
properties: {
invoiceNumber: { type: "string", description: "The invoice or document number" },
invoiceDate: { type: "string", description: "Issue date in YYYY-MM-DD format" },
vendorName: { type: "string", description: "Name of the company that issued the invoice" },
currency: { type: "string", description: "Three-letter currency code (e.g., USD, EUR)" },
lineItems: {
type: "array",
items: {
type: "object",
properties: {
description: { type: "string" },
quantity: { type: "number" },
unitPrice: { type: "number" },
amount: { type: "number" },
},
},
},
subtotal: { type: "number" },
taxAmount: { type: "number" },
totalAmount: { type: "number" },
},
required: ["invoiceNumber", "vendorName", "totalAmount"],
};
const result = await zerox({
filePath: "./invoices/acme-inv-2024-0042.pdf",
openaiAPIKey: process.env.OPENAI_API_KEY,
model: "gpt-4o",
outputSchema: invoiceSchema,
});
const invoiceData = JSON.parse(result.pages[0].content);
console.log(invoiceData.invoiceNumber); // "INV-2024-0042"
console.log(invoiceData.lineItems); // [{description: "Consulting", quantity: 40, ...}]
The description fields in your schema act as lightweight prompts. Adding format hints like "Issue date in YYYY-MM-DD format" significantly improves consistency across different invoice layouts. The model interprets these descriptions to understand what to look for and how to format the result.
This JSON schema extraction approach is central to converting invoice data to structured JSON at scale, and Zerox makes it accessible without writing any prompt engineering boilerplate.
Switching providers
Zerox isn't limited to OpenAI. If you want to use Gemini for its larger context window or lower cost per token, swap the configuration:
const result = await zerox({
filePath: "./invoices/acme-inv-2024-0042.pdf",
model: "gemini-2.0-flash",
outputSchema: invoiceSchema,
credentials: {
apiKey: process.env.GOOGLE_API_KEY,
},
});
The same schema works across providers. This makes Zerox useful for benchmarking different vision models against the same set of invoices without rewriting your extraction logic.
Where Zerox fits and where it doesn't
Zerox is the fastest path from zero to working invoice extraction in Node.js. It abstracts away image conversion, prompt construction, and API call management. For teams processing a few hundred invoices a month with standard layouts, it's often enough.
The tradeoff is control. Zerox owns the system prompt sent to the vision model, so you can't fine-tune extraction instructions for unusual invoice formats. You also get limited visibility into error handling — if a model returns malformed JSON or hallucinates a field, your retry and validation logic has to wrap around Zerox rather than being built into the extraction flow. Custom retry strategies, confidence scoring, or multi-pass verification all require working outside the library's abstraction.
Calling GPT-4o and Claude Directly with Structured Output
When you need to customize extraction prompts per document type, control retry behavior, select specific model versions, or integrate extraction into a larger pipeline with precise error handling, calling the vision APIs directly is the better path.
The core pattern is the same regardless of which model you use: convert the invoice page to a base64-encoded image, send it to the model's API alongside an extraction prompt, and use structured output to get typed data back. The differences are in the SDK ergonomics and how each provider handles structured responses.
Defining Your Invoice Schema with Zod
Before making any API calls, define the shape of the data you want back. A Zod schema serves double duty here: it tells the model exactly what structure to return, and it gives you runtime type validation on the response.
import { z } from "zod";
const LineItemSchema = z.object({
description: z.string(),
quantity: z.number(),
unit_price: z.number(),
total: z.number(),
});
const InvoiceSchema = z.object({
invoice_number: z.string(),
invoice_date: z.string(),
vendor_name: z.string(),
line_items: z.array(LineItemSchema),
subtotal: z.number(),
tax: z.number(),
grand_total: z.number(),
});
type Invoice = z.infer<typeof InvoiceSchema>;
This schema covers the fields that matter for most AP workflows: header-level metadata, itemized line items with quantity and pricing, and the financial summary. You can extend it with fields like PO number, payment terms, or currency code as needed.
Preparing the Invoice Image
Both the OpenAI and Anthropic APIs accept base64-encoded images. Here is a shared utility you will reuse across both integrations:
import * as fs from "fs";
import * as path from "path";
function encodeInvoiceImage(filePath: string): {
base64: string;
mediaType: string;
} {
const buffer = fs.readFileSync(filePath);
const base64 = buffer.toString("base64");
const ext = path.extname(filePath).toLowerCase();
const mediaTypes: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".gif": "image/gif",
};
return {
base64,
mediaType: mediaTypes[ext] || "image/png",
};
}
For multi-page PDFs, you will need to render each page to an image first. Libraries like pdf2pic handle this conversion:
import { fromPath } from "pdf2pic";
const converter = fromPath("./invoices/acme-inv-2024-0042.pdf", {
density: 300,
format: "png",
});
const pageImage = await converter(1); // Convert page 1 to PNG
// Then pass pageImage.path to encodeInvoiceImage()
If your invoices are already single-page images (scans, photos, exported PNGs), you can skip that step entirely.
GPT-4o Vision with Structured Output
OpenAI's structured output feature pairs directly with Zod through the openai Node SDK. When you pass a Zod schema, the API guarantees the response conforms to that shape — no parsing failures, no malformed JSON to handle.
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
const openai = new OpenAI();
async function extractInvoiceWithGPT4o(
imagePath: string
): Promise<Invoice> {
const { base64, mediaType } = encodeInvoiceImage(imagePath);
const response = await openai.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content:
"You are an invoice data extraction assistant. Extract all requested fields from the invoice image. Return exact values as they appear on the document. For numerical fields, return the raw number without currency symbols.",
},
{
role: "user",
content: [
{
type: "text",
text: "Extract the structured data from this invoice.",
},
{
type: "image_url",
image_url: {
url: `data:${mediaType};base64,${base64}`,
detail: "high",
},
},
],
},
],
response_format: zodResponseFormat(InvoiceSchema, "invoice"),
});
const result = response.choices[0].message.parsed;
if (!result) {
throw new Error("No parsed response returned from GPT-4o");
}
return result;
}
A few things worth noting about this implementation. The detail: "high" parameter tells the API to process the image at higher resolution, which matters for invoices with small text or dense tables. The zodResponseFormat helper converts your Zod schema into the JSON schema format OpenAI expects, and the SDK handles parsing and validation automatically. What you get back is a fully typed Invoice object, not a raw string you need to parse.
Claude Vision with Structured Output
The Anthropic API takes a slightly different approach to structured output. Rather than a dedicated schema parameter, you guide Claude's response format through the system prompt and then validate with Zod on the client side.
import Anthropic from "@anthropic-ai/sdk";
import { zodToJsonSchema } from "zod-to-json-schema";
const anthropic = new Anthropic();
async function extractInvoiceWithClaude(
imagePath: string
): Promise<Invoice> {
const { base64, mediaType } = encodeInvoiceImage(imagePath);
const jsonSchema = zodToJsonSchema(InvoiceSchema);
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
system: `You are an invoice data extraction assistant. Extract all requested fields from the invoice image and return ONLY valid JSON matching this schema:\n\n${JSON.stringify(jsonSchema, null, 2)}\n\nReturn exact values from the document. For numerical fields, return raw numbers without currency symbols. Do not include any text outside the JSON object.`,
messages: [
{
role: "user",
content: [
{
type: "image",
source: {
type: "base64",
media_type: mediaType as
| "image/png"
| "image/jpeg"
| "image/webp"
| "image/gif",
data: base64,
},
},
{
type: "text",
text: "Extract the structured invoice data from this image.",
},
],
},
],
});
const textBlock = response.content.find((block) => block.type === "text");
if (!textBlock || textBlock.type !== "text") {
throw new Error("No text response returned from Claude");
}
const parsed = JSON.parse(textBlock.text);
return InvoiceSchema.parse(parsed);
}
The Claude integration requires an extra validation step compared to OpenAI's built-in schema enforcement. You convert the Zod schema to JSON Schema for the prompt (using zod-to-json-schema), then validate the response with InvoiceSchema.parse() after parsing the JSON. If Claude returns a field with the wrong type or omits a required field, Zod throws a descriptive error you can catch and retry.
Tradeoffs of Direct API Integration
Calling GPT-4o and Claude directly gives you full control over every aspect of the extraction pipeline:
- Prompt engineering per document type. You can write specialized prompts for different invoice layouts, add few-shot examples for tricky vendors, or include field-level instructions ("dates should be in ISO 8601 format").
- Model selection and fallback. Start with a cheaper model, fall back to a more capable one if validation fails. Switch between providers without changing your data layer.
- Retry and error handling. Implement exponential backoff, circuit breakers, or quality-check loops that re-extract when confidence is low.
- Response post-processing. Normalize currencies, validate totals against line items, flag discrepancies before the data hits your database.
The cost is more code to write and maintain. You own the image conversion, the prompt management, the response parsing, and the error recovery. For a single invoice type at low volume, this is manageable. At scale across dozens of vendor formats, the maintenance burden compounds.
How Vision LLMs Compare to Traditional OCR for Invoices
Before vision LLMs entered the picture, Node.js developers had two primary tools for invoice extraction: tesseract.js for image-based invoices and pdf-parse for native (text-layer) PDFs.
The workflow looked the same in both cases. Extract raw text, then write custom parsing logic — regex patterns, string splitting, coordinate-based heuristics — to map that text onto invoice fields like vendor name, line items, totals, and tax amounts. If you have worked with traditional approaches to extracting invoice data with Node.js, you know how brittle this pipeline becomes once invoice formats start varying.
What Traditional OCR Actually Does (and Doesn't Do)
tesseract.js runs optical character recognition on rasterized images. It converts pixels to characters. That is the extent of its understanding — it has no concept of what a "line item" is or that the number $1,250.00 sitting in the third column of a table represents a subtotal rather than a unit price.
pdf-parse takes a different path. It reads the text layer embedded in native PDFs directly, bypassing OCR entirely. This makes it fast, deterministic, and free of character-recognition errors. But it shares the same fundamental limitation: it outputs a stream of text with no structural awareness.
In both cases, you supply the document understanding. Every invoice layout needs its own parsing rules, and a new vendor format means new regex patterns or positional logic.
Where Vision LLMs Pull Ahead
Vision LLMs process the invoice as a document image and reason about its visual structure. This changes the extraction problem in three specific ways:
- Layout comprehension. A vision LLM understands that a value in the rightmost column of a row belongs to that row's line item. It reads tables the way a human does — spatially — rather than as a flat text dump where column alignment is lost.
- Format generalization. One prompt handles invoices from dozens of vendors with different layouts, languages, and formatting conventions. No per-format parsing rules. No maintenance burden when a vendor changes their template.
- Direct structured output. The model returns typed fields (or JSON matching your Zod schema) in a single step. There is no intermediate "raw text" stage where parsing errors compound.
The practical result: vision LLM vs OCR invoice accuracy diverges sharply as format variation increases. On a batch of invoices from a single vendor with a clean, consistent template, traditional OCR plus well-tuned regex can match or even outperform an LLM (and do so faster and cheaper). On a mixed batch from twenty different vendors — varying table layouts, merged cells, multi-currency formats, handwritten annotations — the LLM maintains consistent field-level accuracy while regex-based parsing breaks down.
Where Traditional OCR Still Makes Sense
Vision LLMs are not a universal replacement. Traditional tools remain the better choice in several real scenarios:
Native PDFs with consistent formats. If your invoices are text-based PDFs from a small set of known vendors, pdf-parse extracts text instantly with zero API cost. Pair it with targeted regex and you get a pipeline that is fast, deterministic, and runs entirely on your own infrastructure.
Offline or air-gapped environments. tesseract.js and pdf-parse run locally. No network calls, no third-party data processing. For organizations with strict data residency or compliance requirements, this matters more than accuracy gains.
High-volume, cost-sensitive workloads. Processing 100,000 invoices per month through GPT-4o or Claude at $0.01–$0.03 per page adds up. If 90% of those invoices come from five vendors with stable templates, a traditional pipeline handles them at negligible marginal cost. Reserve the LLM for the remaining 10% that defy pattern matching.
Deterministic output requirements. LLMs are probabilistic. The same invoice processed twice might yield slightly different formatting in edge-case fields. Traditional OCR with fixed parsing rules produces identical output every time.
What Actually Drives Accuracy Differences
Vague claims about accuracy percentages are not useful without context. The factors that determine whether a vision LLM or traditional OCR performs better on your invoices are specific and measurable:
- Format diversity — the single biggest predictor. More vendor formats means faster degradation for regex-based parsing.
- Table complexity — nested tables, merged cells, and multi-line descriptions trip up text-stream parsing but are handled naturally by vision models.
- Document quality — scanned invoices with skew, noise, or low resolution hurt tesseract.js accuracy. Vision LLMs are more tolerant of degraded inputs, though not immune.
- Field ambiguity — invoices with multiple date fields (invoice date, due date, delivery date) or multiple totals (subtotal, tax, shipping, grand total) require contextual understanding that raw text extraction cannot provide.
If you want to evaluate these factors systematically for your own document set, the guide on measuring and benchmarking invoice OCR accuracy walks through field-level evaluation methodology.
Most production pipelines benefit from using both, routing documents to the right tool based on their characteristics.
Cost, Latency, and Edge Cases in Production
Three factors determine whether a vision LLM extraction pipeline survives contact with production: per-page API costs, response latency, and the edge cases that silently corrupt your output.
Node.js AI Invoice Processing Cost
Vision API pricing is based on input tokens, and images are expensive inputs. A single invoice page image typically consumes 1,000 to 1,500 tokens at standard resolution, with high-detail mode pushing that significantly higher.
Realistic per-page cost estimates at current pricing:
| Model | Approx. Cost Per Page | 1,000 Pages/Month |
|---|---|---|
| GPT-4o | $0.01–$0.03 | $10–$30 |
| Claude Sonnet | $0.01–$0.02 | $10–$20 |
| GPT-4o mini | $0.003–$0.008 | $3–$8 |
These figures account for both input (image + prompt) and output (extracted JSON) tokens. The critical detail: multi-page invoices multiply cost linearly. A three-page invoice costs three API calls, not one. A supplier that routinely sends 8-page invoices with detailed line items will blow past your cost projections if you budgeted based on single-page samples.
Output token costs also vary by how much data you extract. A sparse header-only extraction is cheap. A full line-item extraction with descriptions, quantities, unit prices, tax rates, and totals per line generates substantially more output tokens.
Latency Realities
Vision model API calls are not fast. Expect 2–6 seconds per page depending on model, document complexity, and current API load. A five-page invoice processed sequentially takes 10–30 seconds.
For batch processing, parallel API calls are non-negotiable. Node.js handles this well with Promise.all or a concurrency-limited queue, but you need to balance parallelism against rate limits. A common pattern:
- Process pages within a single invoice in parallel
- Throttle concurrent invoice-level processing to stay within rate limits
- Use a queue (Bull, BullMQ) for large batch jobs
For comparison, tesseract.js processes a page in 200–800 milliseconds locally with zero network dependency. If your pipeline needs sub-second response times per page, vision LLMs are not the right tool without a caching layer in front.
Edge Cases That Break Extraction
These are the failure modes you will encounter in production, roughly ordered by how often they cause real problems.
Multi-page invoices. Vision models process one image at a time. When line items start on page one and continue on page two, or when the invoice header is on a different page than the totals, the model has no cross-page context. Your application layer needs page-stitching logic: extract from each page independently, then merge results by matching partial tables, reconciling duplicate headers, and validating that line item totals sum to the invoice total. This is not trivial code, and it breaks differently for every invoice layout.
Dense table misalignment. Vision models can misread columns in tightly packed line-item tables. A quantity of "2" might get assigned to the wrong line, or a unit price might merge with a description field. Structured output schemas (Zod, JSON Schema) reduce this by constraining the output shape, but they cannot fix a model that read the wrong cell. Invoices with narrow column gutters, merged cells, or inconsistent row heights are the worst offenders.
Currency and date ambiguity. An invoice showing "01/02/2026" could be January 2nd or February 1st depending on the issuing country. A "£" symbol on a low-resolution scan might get read as "$" or "€". International invoices with mixed formats in a single document (header in one locale, line items in another) are surprisingly common. Without explicit prompt instructions specifying expected formats or locale hints, the model will guess, and it will sometimes guess wrong.
Low-quality scans and phone photos. Vision models handle degraded inputs better than traditional OCR, but they still have limits. Skewed photos, heavy compression artifacts, faded thermal paper receipts, and handwritten annotations over printed text all reduce extraction accuracy. The failure mode is subtle: the model returns confident-looking JSON with incorrect values rather than signaling uncertainty.
Rate limits and API reliability. Production pipelines processing hundreds or thousands of invoices will hit rate limits. Both OpenAI and Anthropic enforce per-minute token and request limits that vary by pricing tier. You need exponential backoff with jitter, request queuing, and a strategy for what happens when the API is down entirely. A 30-minute outage during a batch processing window can cascade into missed SLAs.
The Hidden Maintenance Burden
Each edge case above requires its own handling code: custom prompt variations for different invoice formats, validation logic that cross-checks extracted totals against line item sums, fallback strategies for when confidence is low, retry mechanisms with circuit breakers. When models update (and they do, without warning), extraction behavior can shift. A prompt that reliably extracted tax breakdowns with one model version may produce different field names or structures after an update.
This ongoing maintenance cost is the part that is hardest to estimate upfront. The initial implementation is the easy part. Keeping it accurate across thousands of invoice variations from hundreds of suppliers, month after month, is where the real engineering effort lives.
Choosing the Right Approach for Your Pipeline
The approaches covered in this guide sit along a spectrum of control versus complexity. Your best option depends on where your project falls on that spectrum right now, and where it needs to be in six months.
Zerox gets you to working extraction fastest, with the tradeoff of limited control over prompting and error handling. Direct API calls give you full control over prompts, Zod schemas, model selection, and retry logic, but you own the entire extraction system and its maintenance. Traditional OCR (tesseract.js, pdf-parse) costs nothing per page and works offline, but requires custom parsing per format and breaks with layout variation.
A managed extraction API handles the infrastructure you would otherwise build yourself: prompt optimization, multi-page handling, format variation, and accuracy validation. With a managed invoice extraction API with a Node.js SDK, you get a one-call extract() method that accepts batches up to 6,000 files and returns structured XLSX, CSV, or JSON output. Credits are shared between web and API usage on the same account with no separate subscription, and the free tier covers 50 pages per month for testing.
Key Deciding Factors
| Factor | Zerox | Direct API | Traditional OCR | Managed API |
|---|---|---|---|---|
| Monthly volume | Low-medium | Any (cost scales linearly) | High volume, low cost | Any (optimized per-page cost) |
| Format variation | Low-medium | High (with prompt tuning) | Low (consistent formats only) | High (handled internally) |
| Accuracy needs | Good enough | High (you control prompts) | Format-dependent | High (multi-model validation) |
| Engineering time | Minimal setup | Significant build + maintain | Moderate build, high maintain | Minimal integration |
| Budget | API costs pass-through | Direct API costs + dev time | Near zero per page | Pay-as-you-go credits |
What Actually Happens in Practice
Many teams start with Zerox or direct API calls for a proof of concept, then discover that the production complexities covered above (multi-page handling, token budgets, edge-case fallbacks, model updates) compound faster than the initial build suggests. A managed extraction API absorbs that complexity — your code calls extract(), receives structured data, and moves on to what matters: what your application does with that data.
Start with Zerox to validate that vision LLMs work for your specific invoices. That answer usually comes in an afternoon. Then make the production decision: if you need deep customization and have the engineering bandwidth, build with direct API calls. If you want high accuracy and structured output without owning the extraction infrastructure, evaluate a managed API. Both are valid paths, and the right one depends on whether extraction is your product or a means to an end.
About the author
David Harding
Founder, Invoice Data Extraction
David Harding is the founder of Invoice Data Extraction and a software developer with experience building finance-related systems. He oversees the product and the site's editorial process, with a focus on practical invoice workflows, document automation, and software-specific processing guidance.
Profile
View author pageEditorial process
This page is reviewed as part of Invoice Data Extraction's editorial process.
If this page discusses tax, legal, or regulatory requirements, treat it as general information only and confirm current requirements with official guidance before acting. The updated date shown above is the latest editorial review date for this page.
Related Articles
Explore adjacent guides and reference articles on this topic.
Extract Invoice Data with JavaScript and Node.js
Extract structured data from invoices using JavaScript and Node.js. Covers PDF parsing, OCR, and managed APIs with production-ready SDK code examples.
AWS Textract for Invoice Processing: An Honest Developer's Guide
Neutral AWS Textract evaluation for invoice extraction: AnalyzeExpense accuracy, engineering overhead, pricing at real volumes, and build-vs-buy decision guide.
Google Document AI Invoice Processing: A Practical Evaluation
A practical evaluation of Google Document AI for invoice extraction: accuracy benchmarks, the June 2026 deprecation, and when a dedicated API is a better fit.
Extract invoice data to Excel with natural language prompts
Upload your invoices, describe what you need in plain language, and download clean, structured spreadsheets. No templates, no complex configuration.