Blog/Guides

Extract Structured JSON from PDFs in Python

Extract PDF fields into JSON with Python, validate the result against a schema, and check page coverage before importing data.

Guides··4 min read

A PDF-to-JSON pipeline needs three explicit decisions: which pages to read, which fields to return, and which results your application will accept. A response that parses as JSON has passed only the first technical hurdle. It can still contain a missing identifier, an incorrect amount, or a row assigned to the wrong invoice.

This tutorial uses Python and POST /v1/pdf/data to extract a small invoice into a defined structure. The downloadable project includes three synthetic invoices, their expected values, a schema, a client, and validation tests. Download the example projects, fixtures, and results.

Choose the output before choosing the extraction method

NeedRouteOutput
Read existing selectable text/v1/pdf/textText and per-page text
Make a scanned PDF searchable/v1/pdf/ocrA PDF with a text layer
Extract business fields/v1/pdf/data with schemaStructured data and optional citations
Recover a readable document representation/v1/pdf/data without schemaMarkdown and per-page Markdown

OCR and structured extraction solve different problems. An invoice number becoming searchable does not tell your application which string is the invoice number. Likewise, a JSON result does not create a searchable PDF. Keep those outputs separate in your application and storage model.

Install and authenticate

The example uses Python 3.11 or newer and the REST API through Requests. From the extracted archive:

python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
npx @relaypdf/cli setup --env .env

Approve the browser login. Load the generated environment file using your shell or deployment environment. With a POSIX shell and the CLI-created file:

set -a
. ./.env
set +a
python extract.py fixtures/invoice-usd.pdf work/invoice.json

The key belongs on your server. The example writes output to your disk and never logs the input document. You can set RELAYPDF_BASE_URL to use a separate test deployment.

Define an acceptance contract

The complete schema lives in extract.py. Money uses decimal strings such as 125.00, avoiding binary floating-point arithmetic in the validation step. Currency is restricted to USD and EUR because those are the two currencies represented by the fixtures. Broaden that contract deliberately for your own documents.

Missing identifiers are nullable. Requiring every field to be a nonempty string encourages applications to accept fabricated defaults or reject documents without an explicit review route. A null invoice number is useful information.

A minimal version of the request is:

import base64
from pathlib import Path
from api import request
from extract import SCHEMA, validate_invoice

result = request("POST", "/v1/pdf/data", {
    "file": base64.b64encode(Path("fixtures/invoice-usd.pdf").read_bytes()).decode(),
    "schema": SCHEMA,
    "prompt": "Extract printed values only. Return null for missing identifiers."
}).json()

if result["pagesAnalyzed"] != result["pageCount"]:
    raise ValueError("Incomplete document coverage")
validate_invoice(result["data"])

For the first fixture, the authored expected values include invoice_number: INV-1042, currency: USD, and total: 125.00. The expected fixture data is ground truth written with the source documents, not a claim that the API always returns those values.

Inspect the actual response

The production run on September 7, 2026 matched all five expected field groups for this invoice, including the complete line-item array. Download the actual response fields and compare them with the source PDF. This single fixture is a reproducibility check, not an accuracy guarantee.

{
  "invoice_number": "INV-1042",
  "vendor": "Example Workshop",
  "currency": "USD",
  "total": "125.00"
}

This excerpt omits the line items and response envelope for readability; the downloadable JSON includes them.

Validate syntax, coverage, and meaning separately

Draft202012Validator checks the returned object against the schema. It verifies types and required properties; it cannot establish that a value appeared on the source page. The jsonschema documentation explains this validation boundary.

The example then checks quantity multiplied by unit price against each line amount and sums the lines against the total. These fixtures intentionally have no tax, discount, freight adjustment, or rounding allocation. A real invoice pipeline must model those separately. Do not “fix” a discrepancy by overwriting an extracted total with the calculated total; preserve both and route the document for review.

Check page coverage before accepting data. The current implementation batches longer documents and can reconcile structured results across batches. That is not a guarantee that every repeated header, subtotal, or continued row will be interpreted correctly. If you send a page selection, compare coverage to that selection instead of to the full document page count.

Handle scans and failures

The API can use a text or vision path; engine identifies the returned path. ocr: true requests vision-based reading for extraction, but it is distinct from producing an OCR PDF. Keep engine, pagesAnalyzed, and any returned citations alongside your validated output for troubleshooting. Treat citations as evidence to inspect, not independent proof of correctness.

The client uses explicit network timeouts. It does not automatically retry this synchronous extraction POST after an ambiguous connection failure. A timed-out request may have completed remotely. For resumable processing, use an appropriately configured async workflow and stable idempotency keys as described in the batch generation guide.

The test suite also verifies rejection of inconsistent totals. The recorded API measurements disclose the environment and any failed extraction attempts; expected JSON and successful service output must never be conflated.

Use the data extraction reference for request fields and the structured extraction tool page for the product surface. For invoice-specific validation, continue with extracting invoice data. For spreadsheet output, use the PDF tables to CSV guide.

Ready to generate?

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