Blog/Guides

Extract Invoice Data from PDFs into Validated JSON

Build an invoice extraction pipeline with line-item checks, nullable identifiers, decimal arithmetic, and an explicit review path.

Guides··4 min read

An invoice extraction workflow should produce both a candidate record and an acceptance decision. The candidate might contain the vendor, invoice number, currency, total, and line items. The acceptance decision asks whether those fields are complete, internally consistent, and supported by the document.

This guide uses three synthetic invoices: a USD layout, a differently styled EUR layout, and an invoice with its number deliberately omitted. The source PDFs and authored expected records are included. Download the example projects, fixtures, and results.

Define the invoice contract

The example expects an invoice number that may be null, a vendor that may be null, a currency, a total, and one or more line items. Each item has a description, integer quantity, unit price, and amount. Amounts are decimal strings with exactly two fractional digits.

This is a deliberately narrow model. It covers the supplied examples, which contain no tax, discount, shipping adjustment, fractional quantity, or currency with a different minor-unit convention. Extend the schema and business rules before applying it to a broader invoice population.

The general PDF-to-JSON tutorial explains the request contract. Here we focus on what happens after a candidate has been returned.

Run the extraction and validation

After installing the archive's Python requirements and loading the environment produced by npx @relaypdf/cli setup --env .env, run:

python extract.py fixtures/invoice-usd.pdf work/invoice-usd.json --csv work/invoice-usd.csv
python extract.py fixtures/invoice-eur.pdf work/invoice-eur.json
python extract.py fixtures/invoice-missing.pdf work/invoice-missing.json

The script verifies full-document coverage, checks the JSON schema, checks line arithmetic, and compares the summed lines with the printed total. It writes the accepted candidate to your local disk. It does not insert records into an accounting system or mark invoices as approved.

The core arithmetic checks use decimal arithmetic:

from decimal import Decimal

for item in data["items"]:
    calculated = Decimal(item["unit_price"]) * item["quantity"]
    if calculated != Decimal(item["amount"]):
        raise ValueError("Line arithmetic mismatch")

calculated_total = sum(
    (Decimal(item["amount"]) for item in data["items"]),
    Decimal(0)
)
if calculated_total != Decimal(data["total"]):
    raise ValueError("Invoice total requires review")

A schema validator establishes structural validity. Arithmetic establishes internal consistency. Neither proves that the candidate accurately represents the source. Two incorrectly extracted numbers can still add up.

Keep missing values visible

The missing-number fixture should produce a null invoice number. An extraction prompt should not ask the model to fill in missing identifiers from a filename, a previous record, or a guessed sequence.

A useful review rule is: if the invoice number or vendor is missing, save the candidate but prevent automatic import. That is separate from structural validation, which deliberately permits null. Add supplier matching, duplicate checks, and account-specific rules only after defining how a reviewer can resolve an ambiguous result.

Preserve the original candidate when a reviewer corrects it. Otherwise you lose the ability to measure which fields the extractor actually got right.

Evaluate field groups with disclosed ground truth

The fixture set checks five groups: invoice number, vendor, currency, total, and the entire ordered line-item array. The downloadable measurement script derives its denominator from the expected record rather than hardcoding an accuracy percentage.

For each group, exact equality is required. Comparing the full item array means a missing row, an extra repeated header, or reordered lines causes the group to fail. This intentionally strict check is useful for a small tutorial fixture; a larger evaluation should also report per-line and per-field results.

The expected JSON is authored ground truth. The recorded service measurements distinguish successful comparisons from request failures. A service outage must not become a zero-error accuracy claim by removing the failed documents from the denominator.

Recorded production extraction results

On September 7, 2026, we submitted each of the three digital invoices once to the production API. All five checked field groups matched for each invoice, including the null invoice number in the missing-number fixture. The line-item array was compared as a complete ordered value. All three responses used the text path.

FixtureExact field groups matchedMissing-number behavior
USD invoice5 of 5Printed identifier retained
EUR invoice5 of 5Printed identifier retained
Invoice without a number5 of 5Returned null

Download the actual USD extraction, EUR extraction, and missing-number extraction. These are three synthetic examples, not evidence of a general accuracy percentage. Earlier staging attempts returned 503 ai_unavailable; those failures are recorded separately in the measurement file.

Separate recognition from business approval

A scanned invoice adds recognition uncertainty. A faint decimal separator or an OCR substitution between 0 and O can pass a string-type check. Inspect source evidence for fields that drive payments or reconciliation, and treat returned citations as review aids.

The searchable OCR experiment includes a degraded scan that completed processing but failed every target-phrase check. That is why a pipeline needs content validation after a successful job status.

Keep review reasons specific: missing identifier, incomplete page coverage, total mismatch, unsupported currency, or unexpected document type. “Extraction failed” gives an operator little information about the next action.

Move from tutorial to a measured workflow

Before enabling automatic imports, build a consented evaluation set representative of your actual suppliers and scan quality. Record results for digital and scanned inputs separately. Include credit notes, multipage invoices, duplicate submissions, and documents that are not invoices at all.

Version the schema, prompt, and acceptance rules together. When a rule changes, rerun the same fixtures so a higher acceptance rate cannot hide a lower correctness rate. Link every imported record to the source and the validation decision.

RelayPDF exposes the extraction operation through PDF data extraction and documents its request fields in the API reference. For a portable spreadsheet output, use PDF tables to CSV.

Ready to generate?

One API for HTML, Markdown, URLs, and Office. REST, SDK, CLI, or MCP.