---
title: "Extract data from PDF: A developer guide to structured JSON"
canonical_url: "https://www.nutrient.io/blog/pdf-data-extraction-developer-guide/"
md_url: "https://www.nutrient.io/blog/pdf-data-extraction-developer-guide.md"
last_updated: "2026-09-10T06:21:41.308Z"
description: "How to extract data from PDF files and scans as structured JSON. A PDF data extraction guide covering parsing, OCR, schemas, source grounding, and review."
---

# Extract data from PDF files: A developer guide to structured data from PDFs and scans

**TL;DR**

- Use a PDF parser for clean, born-digital files when text or coordinates are enough.

- Add optical character recognition (OCR) for scans. Add layout analysis when reading order, tables, or form structure matters.

- Use schema-based extraction when your application needs known fields as typed JSON.

- Add vision language model (VLM) processing for degraded scans, page imagery, or cursive and free-form handwriting.

- Treat confidence as an uncalibrated routing signal. Use match labels and bounding boxes to check each important value against its source.

- Choose open source for stable layouts and full control. Choose an API when document variety and maintenance cost exceed the value of custom rules.

## Why PDFs resist structured extraction

A PDF describes how a page should look, but it doesn’t guarantee a logical record of headings, paragraphs, rows, columns, and fields.

That mismatch creates several extraction problems:

- Text may be split into positioned fragments and stored outside reading order.

- A table may be a set of words and drawn lines rather than a table object.

- A scanned page may contain pixels without a searchable text layer.

- Multicolumn pages require spatial reasoning before text can be read in sequence.

- Visible form values may live in form fields, overlays, flattened page content, or images.

- Checkboxes and handwriting require recognition beyond plain text parsing.

An agent needs more than a text dump. It often needs typed fields, table cells, page references, and evidence that connects each value to the source.

The first design decision is therefore not “Which model should I use?” It’s “What representation does the next system require?”

## PDF extraction techniques: What each layer does

The terms below describe different pipeline stages. They aren’t interchangeable.

### PDF parsing

PDF parsing reads encoded text and page geometry from a born-digital PDF. It can return characters, text blocks, fonts, and coordinates without reading page pixels.

Use parsing for searchable PDFs when the application needs raw text, Markdown, or position data. Parsing alone can’t recover text from an image-only scan.

### Optical character recognition

Optical character recognition converts image pixels into machine-readable characters. It creates a text layer for scanned PDFs, photographs, and other raster documents.

OCR answers “What characters are visible?” It doesn’t inherently identify an invoice total, reconstruct every table, or map values into your business schema.

### Layout analysis

Layout analysis groups text and visual regions into ordered document elements. Those elements can include paragraphs, tables, pictures, formulas, and key-value regions.

Use it when reading order and spatial relationships matter. It sits between character recognition and field-level extraction.

### Document classification

Document classification assigns a document type, such as invoice, contract, or claim form. A pipeline can use that label to select a schema, mode, or review policy.

Classification is a technique and pipeline stage rather than a single product feature. See Nutrient’s [SDK-side document classification](https://www.nutrient.io/guides/ai-document-processing/classify-documents.md) guide for implementation details.

### Schema-based extraction

Schema-based extraction maps document content into a caller-defined JSON object. The schema names fields, types, required values, nested objects, and arrays.

Use it when downstream code expects stable keys such as `invoice_number`, `due_date`, and `line_items` across varying layouts.

### VLM-assisted extraction

VLM-assisted extraction uses a vision language model to interpret page imagery and visual context. It can help with degraded scans, image descriptions, and connected handwriting.

Use it selectively. Visual interpretation adds processing depth and cost, and high-stakes output still needs source checks or human review.

These layers often form one pipeline: parse or OCR, analyze layout, classify if needed, extract to a schema, validate, and route exceptions.

## DIY and open source PDF extraction

An open source stack works well when layouts are stable, output needs are narrow, and your team can maintain the rules.

Common building blocks include:

- **pypdf** — Reads PDF files, iterates through pages, and extracts encoded text.

- **PDFMiner** — Exposes text positions, font details, and lower-level layout information.

- **PyMuPDF** — Extracts text and images, and renders PDF pages.

- **Tabula and Camelot** — Reconstruct tables from text-based PDFs. Camelot supports bordered and spacing-based table strategies.

- **pdfplumber** — Exposes text boxes and lines for custom table logic.

- **Tesseract** — Adds OCR for scans before parsing or field rules run.

This pypdf example checks common input failures and returns one string for downstream processing:

```python

import os

import pypdf

def extract_text_pypdf(pdf_path):
    """Extract text from a PDF and report common input failures."""
    if not os.path.exists(pdf_path):
        return "Error: File not found"

    try:
        with open(pdf_path, "rb") as file:
            reader = pypdf.PdfReader(file)

            if reader.is_encrypted:
                return "Error: PDF is password-protected"

            if len(reader.pages) == 0:
                return "Error: PDF contains no pages"

            text_content = []
            for page_number, page in enumerate(reader.pages, start=1):
                try:
                    page_text = page.extract_text() or ""
                    if page_text.strip():
                        text_content.append(
                            f"--- Page {page_number} ---\n{page_text}"
                        )
                except Exception as page_error:
                    text_content.append(
                        f"--- Page {page_number} ---\n"
                        f"Error extracting text: {page_error}"
                    )

            return "\n\n".join(text_content) or "No readable text found in PDF"
    except PermissionError:
        return "Error: Permission denied accessing file"
    except Exception as error:
        return f"Error processing PDF: {error}"

pdf_path = input("Enter PDF path: ").strip()
print(extract_text_pypdf(pdf_path))

```

Use this approach when the same rules continue to work across a representative sample. Add tests for missing text, changed labels, split tables, and unexpected page order.

Move beyond a DIY stack when new layouts keep creating exceptions. The same applies when you need typed JSON, per-field source locations, multilingual routing, or a review queue.

These tutorials cover the individual building blocks:

- [Extract text from a PDF using Python](https://www.nutrient.io/blog/extract-text-from-pdf-using-python.md)

- [Use Tesseract OCR in Python](https://www.nutrient.io/blog/how-to-use-tesseract-ocr-in-python.md)

- [Tesseract Python guide](https://www.nutrient.io/blog/tesseract-python-guide.md)

- [Extract text from a PDF with PyMuPDF](https://www.nutrient.io/blog/extract-text-from-pdf-pymupdf/)

## Hosted document AI APIs

Hosted document services remove infrastructure and model maintenance from the application team. AWS Textract, Google Document AI, and Azure AI Document Intelligence return JSON with structures such as tables and key-value pairs.

These services are generally priced per page. Compare them on your documents, required fields, output evidence, latency, retention rules, and total processing cost.

Nutrient Data Extraction API exposes two endpoints, and choosing between them is the first decision in any integration:

- `POST https://api.nutrient.io/extraction/parse` returns Markdown, spatial elements, or both. It reads a whole document.

- `POST https://api.nutrient.io/extraction/extract` returns data shaped by an inline JSON Schema. It answers for known fields.

## How to extract data from a PDF using an API

The API flow has three steps, and they’re the same whether the file is born-digital or scanned.

**1. Authenticate.** Send `Authorization: Bearer <API key>` on every request. Keep the key on a backend service.

**2. Submit the document.** Post the file as multipart `file` plus an `instructions` JSON string, or post JSON with a remote `url`. Supported uploads include PDF, DOCX, DOC, XLSX, XLS, PPTX, PPT, RTF, PNG, JPEG, TIFF, BMP, GIF, and WEBP, and the parse endpoint also accepts a raw PDF, PNG, JPEG, or TIFF body. Two decisions belong in `instructions`:

- **Which endpoint.** Use `/extraction/parse` when you want the whole document as Markdown or spatial elements. Use `/extraction/extract` when you already know the fields you need.

- **Which mode.** A born-digital PDF already contains encoded text, so `text` (parse only) or `structure` is usually enough. A scan contains pixels, so it needs an OCR-capable mode — `structure`, `understand`, or `agentic`. There’s no separate OCR flag to set; the mode carries it.

**3. Read the JSON.** Parse responses return `output.elements` (spatial) or Markdown. Extract responses return `output.data` in your schema’s shape, with per-field evidence in `output.metadata`. Both report credit usage under `usage.data_extraction_credits`.

You don’t need to detect the scan yourself before submitting. A single OCR-capable mode handles a mixed batch of native and scanned PDFs, and the recognition and match signals in the response tell you which pages came back weak. The rest of this guide covers the two decisions in step two — mode depth first, then schemas.

## Choosing PDF processing depth

Nutrient’s parse endpoint has four modes. Each mode trades cost for deeper document interpretation.

| Mode         | Cost per page | OCR | Layout depth                 | Output              | Use it for                                                                       |
| ------------ | ------------- | --- | ---------------------------- | ------------------- | -------------------------------------------------------------------------------- |
| `text`       | 1 credit      | No  | Text extraction              | Markdown only       | Born-digital PDFs for search, retrieval-augmented generation (RAG), or migration |
| `structure`  | 1.5 credits   | Yes | Basic segmentation           | Spatial or Markdown | Scans with straightforward layouts                                               |
| `understand` | 9 credits     | Yes | AI-augmented layout analysis | Spatial or Markdown | Tables, forms, formulas, printed-style handwriting, and complex layouts          |
| `agentic`    | 18 credits    | Yes | AI plus VLM analysis         | Spatial or Markdown | Degraded scans, image descriptions, and cursive or free-form handwriting         |

`understand` is the API default. Set the mode explicitly so cost and behavior remain visible in code review.

The following request parses a PDF in `understand` mode and returns spatial elements:

### curl

```shell

curl -X POST https://api.nutrient.io/extraction/parse \
  -H "Authorization: Bearer your_api_key_goes_here" \
  -F "file=@document.pdf" \
  -F 'instructions={"mode":"understand","output":{"format":"spatial"}}'

```

### Python

```python

import json

import requests

with open("document.pdf", "rb") as document:
    response = requests.post(
        "https://api.nutrient.io/extraction/parse",
        headers={
            "Authorization": "Bearer your_api_key_goes_here",
        },
        files={"file": document},
        data={
            "instructions": json.dumps(
                {
                    "mode": "understand",
                    "output": {"format": "spatial"},
                }
            )
        },
        timeout=120,
    )

response.raise_for_status()
result = response.json()

for element in result["output"]["elements"]:
    print(element["type"], element.get("text", ""))

```

Spatial and Markdown output can be requested together with `formats`. Spatial output can also include words, HTML tables, semantic block formatting, headers, footers, or words found inside pictures.

The relevant output defaults are:

- `includeWords: false`

- `useHtmlTables: true`

- `enableSemanticBlockFormatting: true`

- `includeHeadersAndFooters: false`

- `extractWordsFromPictures: false`

Choose a mode with this sequence:

1. Use `text` for born-digital documents when Markdown is enough.

2. Use `structure` when a scan needs OCR and its layout is straightforward.

3. Use `understand` for complex layouts, tables, forms, formulas, or printed-style handwriting.

4. Use `agentic` when tested samples need VLM help with imagery, degradation, or connected handwriting.

5. Use the extract endpoint instead when the application already knows its target fields.

Mixed pipelines can route each document family to the least expensive mode that passes a labeled evaluation set.

## Extracting known fields into structured JSON

The extract endpoint accepts an object-root JSON Schema and returns `output.data` in that shape. Use it when field names are known before processing.

This request extracts three invoice fields:

### curl

```shell

curl -X POST https://api.nutrient.io/extraction/extract \
  -H "Authorization: Bearer your_api_key_goes_here" \
  -F "file=@invoice.pdf" \
  -F 'instructions={"schema":{"type":"object","properties":{"invoice_number":{"type":"string","description":"Invoice identifier"},"total_amount":{"type":"number","description":"Final total after discounts and tax"},"due_date":{"type":"string","format":"date","description":"Payment due date"}},"required":["invoice_number","total_amount"]},"parseConfig":{"mode":"understand"},"options":{"includeCitations":true,"strict":false,"multimodal":false}}'

```

### Python

```python

import json

import requests

schema = {
    "type": "object",
    "properties": {
        "invoice_number": {
            "type": "string",
            "description": "Invoice identifier",
        },
        "total_amount": {
            "type": "number",
            "description": "Final total after discounts and tax",
        },
        "due_date": {
            "type": "string",
            "format": "date",
            "description": "Payment due date",
        },
    },
    "required": ["invoice_number", "total_amount"],
}

instructions = {
    "schema": schema,
    "parseConfig": {"mode": "understand"},
    "options": {
        "includeCitations": True,
        "strict": False,
        "multimodal": False,
    },
}

with open("invoice.pdf", "rb") as document:
    response = requests.post(
        "https://api.nutrient.io/extraction/extract",
        headers={
            "Authorization": "Bearer your_api_key_goes_here",
        },
        files={"file": document},
        data={"instructions": json.dumps(instructions)},
        timeout=120,
    )

response.raise_for_status()
print(json.dumps(response.json()["output"]["data"], indent=2))

```

The extract endpoint accepts `text`, `structure`, `understand`, or `agentic` in `parseConfig.mode`, and it defaults to `understand`.

The supported schema vocabulary is intentionally small.

- Types: `string`, `number`, `integer`, `boolean`, `array`, and `object`.

- Object keywords: `properties` and `required`.

- Array keyword: `items`.

- Guidance: `description` on any supported type.

- String constraints: `enum` and `format: "date"`.

Keep the schema inline. The API doesn’t support `$ref`, definitions, `allOf`, `oneOf`, numeric ranges, other string formats, conditionals, or `additionalProperties`.

The `options` object accepts `includeCitations`, `strict`, and `multimodal`. Their defaults are `true`, `false`, and `false`, respectively. The request object’s optional free-text `instructions` field can contain up to 10,000 characters.

This trimmed response came from a verified extract request. `output.metadata` mirrors the data shape and adds citation details at scalar leaves:

```json

{
  "output": {
    "data": {
      "invoice_number": "INV-2026-0001",
      "total_amount": 1547.5,
      "due_date": "2026-09-01"
    },
    "metadata": {
      "invoice_number": {
        "bbox": { "x": 452, "y": 258, "width": 219, "height": 24 },
        "match": "id_match",
        "confidence": 0.95,
        "confidenceComponents": {
          "groundingScore": 0.95,
          "source": "no-logprobs"
        },
        "pageIndex": 0,
        "pageNumber": 1,
        "source_bboxes": [
          {
            "bbox": {
              "x": 199,
              "y": 164.6,
              "width": 473.9,
              "height": 231.4
            },
            "block_id": "b0",
            "pageIndex": 0,
            "pageNumber": 1
          }
        ]
      }
    },
    "pages": [
      { "page": 1, "width": 1200, "height": 1697 }
    ]
  }
}

```

Responses also report `usage.data_extraction_credits.cost` and `usage.data_extraction_credits.remainingCredits`. The Runs API can retrieve stored runs when run storage is enabled.

## Trusting extracted data

Structured JSON proves that a response matches a shape. It doesn’t prove that each value came from the correct place.

With citations enabled, each scalar metadata leaf can include a page, bounding box, match label, confidence signal, and source block references.

`source_blocks` lists source block IDs. `source_bboxes` adds each block’s bounding box, block ID, page index, and page number.

The `match` field explains how a value was grounded:

| Match label           | Meaning                                              |
| --------------------- | ---------------------------------------------------- |
| `id_match`            | One source block matched exactly.                    |
| `id_match_multiblock` | Source text matched across multiple blocks.          |
| `id_match_partial`    | Only some cited source blocks were resolved.         |
| `fuzzy_match`         | The value approximately matched source text.         |
| `not_found`           | The value couldn’t be grounded to a source location. |

The `confidence` value ranges from zero to one, but it’s relative and uncalibrated; it isn’t a probability or percentage.

An absent `confidence` value means that no score was available. It doesn’t mean low confidence.

`confidenceComponents` can include `probabilityScore`, `marginScore`, `groundingScore`, and `formatScore`. Its `source` value is `logprobs+margin`, `logprobs-only`, or `no-logprobs`.

`recognitionScore` reports OCR legibility as the minimum recognition score across matched source blocks. It’s omitted for born-digital text, `not_found`, and VLM-only extractions.

Bounding boxes use a top-left origin. Use render-space pixels when the page includes width and height; otherwise, use PDF points.

This function flags top-level fields with weak grounding or a low available score:

```python

def fields_needing_review(metadata, threshold=0.7):
    """Return top-level fields whose citations suggest manual review."""
    flagged = []

    for field, citation in metadata.items():
        if not isinstance(citation, dict):
            continue

        match = citation.get("match")
        confidence = citation.get("confidence")

        if match in ("fuzzy_match", "not_found"):
            flagged.append(field)
        elif confidence is not None and confidence < threshold:
            flagged.append(field)

    return flagged

output = response.json()["output"]
print(fields_needing_review(output.get("metadata", {})))

```

Treat `0.7` as an example, not a universal cutoff. Select thresholds against a labeled sample from each document family and field risk class.

Read the [citations and confidence guide](https://www.nutrient.io/guides/dws-data-extraction/extract/citations-and-confidence.md) for nested traversal and coordinate details. The [confidence score article](https://www.nutrient.io/blog/document-extraction-confidence-scores.md) explains why source evidence matters alongside a score.

## Extracting tables, forms, checkboxes, handwriting, and multilingual content

Spatial parse output uses typed elements. The union includes `paragraph`, `formula`, `picture`, `table`, `keyValueRegion`, and `handwriting`.

Common fields include `id`, `type`, `bounds`, `confidence`, `readingOrder`, and page metadata. Bounds contain `x`, `y`, `width`, and `height`.

Page metadata contains `pageIndex`, `pageNumber`, `width`, and `height`. Formula elements add `latex`. Picture elements add `classification`, `classificationConfidence`, and `altDescription`.

### Tables

A `table` element contains `rowCount`, `columnCount`, and `cells`. Each cell can include its row, column, row span, column span, and text.

Use spatial table output when the application needs reconstructed cells. Use schema arrays when the application needs known rows mapped into business objects.

### Forms and key-value pairs

A `keyValueRegion` contains pairs of key and value entities plus `relationshipConfidence`.

This element isn’t guaranteed for every label-and-value layout. A simple form can instead return paragraphs or a table.

If fields are known, request them through the extract endpoint with a schema. Don’t make application correctness depend on every document producing `keyValueRegion` elements.

### Checkboxes

Checkbox state is a paragraph role, not a separate spatial element type. The relevant roles are `CheckboxSelected` and `CheckboxUnselected`.

Other paragraph roles include `Title`, `SectionHeader`, `Header`, `Footer`, `Caption`, `Footnote`, `ListItem`, `PageNumber`, and `Code`.

### Handwriting

Use `understand` for clearly separated, printed-style handwriting. Use `agentic` for cursive, connected, or free-form handwriting.

There’s no handwriting or intelligent character recognition request toggle. Handwriting appears as an output element when detected.

Even agentic output can be confidently wrong. Route high-stakes handwritten fields to review and show the cited page region.

### Multilingual documents

The API recognizes more than 100 languages, but it doesn’t detect them. It defaults to English (`eng`), so a German or Japanese scan submitted without a language hint is read by an English model — the quiet kind of wrong text described earlier.

Set `options.language` to a language name or an ISO 639-2 code (`"german"` or `"deu"`), and pass an array when a document mixes them. The option applies only to the OCR modes; `text` mode reads encoded characters and never looks at pixels, so the setting has no effect there.

Refer to the [supported languages guide](https://www.nutrient.io/guides/dws-data-extraction/supported-languages.md) for the current list. Recognizing a language isn’t translating it.

## Document data extraction in production

A pipeline that passes a curated test set will still encounter documents it wasn’t designed for. Production traffic is where data extraction from PDF files stops being a parsing problem and becomes a routing problem.

These are the failure modes that show up first.

### Poor scan quality

Faxed, photocopied, and phone-camera pages arrive skewed, low-contrast, or compressed to the point where characters blur together. OCR still returns text — it just returns the wrong text, without complaining.

`recognitionScore` is the signal here. It reports OCR legibility as the minimum recognition score across matched source blocks, so a single unreadable region pulls the field’s score down rather than being averaged away. It’s omitted for born-digital text, so its absence isn’t a failure.

`agentic` is the mode intended for degraded scans, because its VLM stage interprets page imagery rather than relying on the character layer alone. Whether it recovers a given document family is a question for your samples, not an assumption to build routing on.

### Tables that span pages

Spatial elements carry a single `pageIndex` and `pageNumber`, so a table that continues past a page break arrives as separate elements tied to separate pages. Relating them is application work. The documents make it harder: Column headers may repeat on each page, appear only on the first, or be restated in a slightly different form.

Whether continuation rows land in a single schema array is a property of the documents rather than a guarantee, so confirm it on multipage samples before relying on it.

Whichever route you take, subtotal and carry-forward rows are the ones that quietly double a total. Use the `description` on each array item to exclude them, and reconcile the extracted rows against a document total.

### Annotations and overlays

A value added as a PDF annotation, a stamp, or a flattened overlay can sit outside the original page content. A field then holds the printed value while the operative one is written beside it — an amended figure, a countersigned date, a struck-through line item.

Where both can occur, extract both and let a business rule decide which wins. Signatures, initials, ticked boxes, and margin notes are the least predictable content on a page and the most likely to carry the meaning that matters.

### Multicolumn and mixed layouts

Two-column contracts, sidebars, footnotes, and pages that switch column count mid-document all break naive reading order. Text that’s correct character by character can still be sequenced into nonsense.

Use `readingOrder` on spatial elements rather than sorting by y-coordinate, and use `understand` or deeper for any document family where columns vary. `text` mode does no layout analysis at all.

### Fallback logic

The routing rule that survives contact with production is: run the cheapest mode that passes, measure the result, and escalate only the documents that fail.

This function starts at the mode you expect to work, checks the result with the review rules defined earlier, and escalates only when fields come back ungrounded. The ladder begins at `structure` rather than `text` because `text` runs no OCR — it has nothing to read on anything scanned:

```python

import json

import requests

MODE_LADDER = ["structure", "understand", "agentic"]

def extract_with_fallback(pdf_path, schema, start_mode="structure"):
    """Extract fields, escalating modes only while fields fail their checks."""
    ladder = MODE_LADDER[MODE_LADDER.index(start_mode) :]
    last_output = None
    flagged = []

    for mode in ladder:
        instructions = {
            "schema": schema,
            "parseConfig": {"mode": mode},
            "options": {"includeCitations": True},
        }

        with open(pdf_path, "rb") as document:
            response = requests.post(
                "https://api.nutrient.io/extraction/extract",
                headers={
                    "Authorization": "Bearer your_api_key_goes_here",
                },
                files={"file": document},
                data={"instructions": json.dumps(instructions)},
                timeout=120,
            )

        response.raise_for_status()
        last_output = response.json()["output"]
        flagged = fields_needing_review(last_output.get("metadata", {}))

        if not flagged:
            return {"mode": mode, "output": last_output, "review": []}

    return {"mode": ladder[-1], "output": last_output, "review": flagged}

```

Three things make this safe to run at volume. The ladder is bounded, so a document that fails everywhere ends in a review queue instead of a retry loop. The escalation is per document, not per batch, so one bad scan doesn’t move a whole day’s traffic to 18 credits a page. And the final return still carries its output and its flagged fields, so a reviewer sees the best available answer next to the reason it’s uncertain.

Log the mode each document settled on. When a document family starts escalating more often than it used to, that’s a change in the incoming documents, and it’s visible weeks before anyone notices wrong values downstream.

## Human review as a pipeline stage

Human review is exception handling, not a fallback added after launch. Define its routing rules before extracted values can trigger payments, decisions, or record updates.

A practical review item contains:

- The field name, extracted value, and expected type.

- The page image with the cited bounding box highlighted.

- The match label, available confidence signals, and recognition score.

- The document family and business rule that triggered review.

- A correction action that records the reviewed value.

Route `fuzzy_match`, `not_found`, missing required evidence, and sample-calibrated low scores to review. Always review fields whose failure cost exceeds the automation benefit.

The [extraction-to-action article](https://www.nutrient.io/blog/ai-document-automation-extraction-to-action.md) covers review in a wider automation pipeline. The [React PDF viewer tutorial](https://www.nutrient.io/blog/how-to-build-a-reactjs-file-viewer.md) shows how to build a document viewing surface for custom workflows.

## Cloud API vs. self-hosted processing

Use a cloud extraction API when managed processing fits your data policy and operating model. It keeps the integration focused on requests, outputs, and review logic.

Use [Nutrient Document Engine](https://www.nutrient.io/sdk/document-engine/) as the starting point for self-hosted document processing. Use the [Nutrient OCR SDKs](https://www.nutrient.io/sdk/ocr/) for in-app viewing and OCR on supported web and mobile surfaces.

These are different product paths. Verify the required extraction features and deployment constraints before assuming API parity.

## Security and data lifecycle

Keep the bearer API key on a trusted backend. Don’t embed it in browser code, mobile bundles, logs, or source control.

Send only documents that the caller is authorized to process. Apply your own access control before the extraction request and before returning stored results.

Retention behavior depends on the plan and whether data retention is enabled, so confirm the policy for your account before production use.

The service rejects unencrypted HTTP requests and uses HTTPS with TLS for API communication. Review the [Data Extraction API security guide](https://www.nutrient.io/guides/dws-data-extraction/security.md) and [Nutrient privacy policy](https://www.nutrient.io/legal/privacy/) for current handling terms.

Avoid logging document bodies or extracted sensitive values by default. Log request identifiers, processing decisions, review outcomes, and credit usage according to your own retention policy.

## Decision framework for PDF data extraction

Start with a representative labeled set. Include clean PDFs, scans, layout variants, long tables, checked forms, handwriting, and every language you expect.

Published numbers narrow the shortlist but don’t replace that set. The [Data Extraction API benchmarks](https://www.nutrient.io/api/data-extraction-api/benchmarks/) show how the modes score on public document collections; your own documents decide which mode you ship.

Then answer these questions:

1. **What output does the next system need?** Choose Markdown for retrieval, spatial elements for layout-aware consumers, or schema-shaped JSON for known fields.

2. **Does the file contain encoded text?** Use parsing for born-digital files and OCR for scans.

3. **How complex is the layout?** Use structure for basic segmentation, understand for complex structure, and agentic for tested VLM-dependent cases.

4. **Are the fields known?** Use the extract endpoint with a schema instead of reconstructing them from parse elements.

5. **How will you verify important values?** Require citations, route weak matches, and tune thresholds on labeled examples.

6. **Can the document leave your environment?** Choose a cloud or self-hosted product path based on policy and required features.

7. **Who owns exceptions?** Define the review queue, correction action, and downstream retry behavior.

Use open source when layouts are stable, rules remain small, and your team benefits from owning the entire stack. Include maintenance, OCR tuning, evaluation, and review tooling in the cost.

Use a hosted API when varied documents make custom rules expensive or when structured output and source grounding would otherwise require several services.

Don’t begin with the deepest mode for every page. Measure field quality and review volume. Then route each document family to the least expensive passing path.

## Conclusion

To extract data from a PDF reliably, treat it as a layered engineering problem rather than a single call. Parsing reads encoded content, OCR reads pixels, layout analysis rebuilds structure, and schema extraction produces agent-usable JSON.

In production, the boundary is trust. Ground important values in source blocks, treat confidence as uncalibrated, and send ambiguous or high-impact fields to review.

Start with the [Nutrient Data Extraction API](https://www.nutrient.io/api/data-extraction-api/) and the [Data Extraction API guides](https://www.nutrient.io/guides/dws-data-extraction.md). Test the four processing modes on your own documents before fixing routing and review thresholds.

**Call to Action**

Extract typed JSON from your own PDFs and scans

[Learn More](https://www.nutrient.io/api/data-extraction-api/)

## FAQ

#### How do I extract data from a PDF into JSON?

Send the file to an extraction endpoint with a bearer API key and a JSON Schema describing the fields you need. Then read the typed object from the response. With Nutrient, that’s a `POST` to `/extraction/extract` with the schema inline in `instructions`; the response returns `output.data` in your schema’s shape and per-field source evidence in `output.metadata`. The same request works for born-digital PDFs and scans, provided the mode is OCR-capable.

#### What is the best way to extract structured data from a PDF with AI?

There is no universal best method. Use schema-based extraction when you know the target fields, and select the shallowest processing mode that passes a representative evaluation set. Add source grounding and review rules before automating high-impact actions.

#### What should developers compare when choosing a PDF data extraction API?

Compare support for born-digital files and scans, schema-shaped JSON, tables, handwriting, languages, source locations, and review signals. Also compare latency, page cost, retention policy, deployment options, and behavior on your own labeled documents.

#### How do I get confidence scores and source grounding from PDF extraction?

Enable citations on the extract request. Nutrient returns metadata leaves with fields such as `match`, `confidence`, `bbox`, page references, and source blocks. Treat confidence as relative and uncalibrated, and use match labels as direct grounding outcomes.

#### Can I extract data from scanned PDFs and handwriting?

Yes. Scans require an OCR-capable mode. Use understand mode for printed-style handwriting and agentic mode for cursive, connected, or free-form handwriting. Then review high-stakes fields.

#### Should I use open source or an API for PDF data extraction?

Use open source when layouts are stable and your team can maintain OCR, parsing, tests, and exception rules. Use an API when document variety, schema mapping, source grounding, or review tooling would create a larger custom system.

#### What is the difference between PDF parsing and OCR?

PDF parsing reads text and geometry already encoded in a born-digital file. OCR recognizes characters from page pixels. Scanned PDFs need OCR before text or fields can be extracted.

#### Can a PDF extraction API return tables, key-value pairs, and checkboxes?

Spatial output can contain tables and key-value regions. Key-value regions depend on the document, while checkbox state appears through selected or unselected paragraph roles. Use schema extraction when known fields must have stable JSON keys.

#### Does a high confidence score mean an extracted field is correct?

No. Nutrient’s field confidence is a relative, uncalibrated signal rather than a probability. Check the match label and cited page region, and retain review for high-stakes fields.

## Related reading

- [Document AI vs. traditional OCR](https://www.nutrient.io/blog/document-ai-vs-ocr.md)

- [Handwriting recognition with OCR and VLMs](https://www.nutrient.io/blog/handwriting-recognition-ocr-vlm/)

- [PDF extraction benchmark: OpenDataLoader Bench](https://www.nutrient.io/blog/pdf-extraction-benchmark-opendataloader-bench.md)

- [PDF extraction case studies](https://www.nutrient.io/blog/pdf-extraction-document-case-studies.md)

- [What is intelligent document processing?](https://www.nutrient.io/blog/what-is-intelligent-document-processing.md)

- [Introducing Nutrient Data Extraction API](https://www.nutrient.io/blog/introducing-nutrient-data-extraction-api/)

- [Agentic RAG for document workflows](https://www.nutrient.io/blog/agentic-rag.md)
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [How to build an AI agent for contract redlining against a compliance playbook](/blog/ai-contract-redlining-compliance-playbook.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Amazon Textract Alternatives](/blog/amazon-textract-alternatives.md)
- [Start (clears any prior buffer), navigate the document, then stop into a file.](/blog/android-faster-pdf-rendering.md)
- [Android Pdf Out Of Memory Handling](/blog/android-pdf-out-of-memory-handling.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Approval Workflow Software](/blog/approval-workflow-software.md)
- [Approvals Matrix](/blog/approvals-matrix.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Simple PII redaction.](/blog/automated-pii-removal.md)
- [Best Ai Document Workflow Platforms](/blog/best-ai-document-workflow-platforms.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Classification Platforms](/blog/best-document-classification-platforms.md)
- [Best document parser for RAG: LlamaParse vs. Unstructured vs. Reducto vs. Nutrient](/blog/best-document-parser-llamaparse-unstructured-reducto.md)
- [Best Document Parsing Apis](/blog/best-document-parsing-apis.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [Best Multilingual Ocr Software](/blog/best-multilingual-ocr-software.md)
- [Best Secure Document Collaboration Platforms](/blog/best-secure-document-collaboration-platforms.md)
- [Bpm Guide](/blog/bpm-guide.md)
- [Bpm Tools](/blog/bpm-tools.md)
- [Build Vs Buy Document Extraction](/blog/build-vs-buy-document-extraction.md)
- [Business Automation](/blog/business-automation.md)
- [Capex Vs Opex](/blog/capex-vs-opex.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extend Alternatives](/blog/extend-alternatives.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Html To Pptx](/blog/how-to-convert-html-to-pptx.md)
- [Quarterly report](/blog/how-to-convert-pdf-to-markdown-using-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [How To Programmatically Create And Fill Pdf Form In Angular](/blog/how-to-programmatically-create-and-fill-pdf-form-in-angular.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Html To Pdf In Javascript](/blog/html-to-pdf-in-javascript.md)
- [Invoice Approval Software](/blog/invoice-approval-software.md)
- [Javascript Document Editor](/blog/javascript-document-editor.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Langextract Vs Llamaindex Extraction Comparison](/blog/langextract-vs-llamaindex-extraction-comparison.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [Uses OpenAI by default — set OPENAI_API_KEY.](/blog/llamaindex-vs-langchain-rag.md)
- [Llamaparse Alternatives](/blog/llamaparse-alternatives.md)
- [Low Code No Code Document Integrations](/blog/low-code-no-code-document-integrations.md)
- [Material Requisition](/blog/material-requisition.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Flutter 6 Bindings Api](/blog/nutrient-flutter-6-bindings-api.md)
- [Nutrient Flutter Bindings Architecture](/blog/nutrient-flutter-bindings-architecture.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [PDF accessibility for developers: Meeting WCAG 2.2, Section 508, and PDF/UA with an SDK](/blog/pdf-accessibility.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [People Process Tools](/blog/people-process-tools.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [React Pdf Annotation Layer Forms](/blog/react-pdf-annotation-layer-forms.md)
- [React Pdf Custom Rendering Hooks](/blog/react-pdf-custom-rendering-hooks.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [React Pdf Loading States Errors Passwords](/blog/react-pdf-loading-states-errors-passwords.md)
- [React Pdf Outline Table Of Contents](/blog/react-pdf-outline-table-of-contents.md)
- [React Pdf Setup Basic Rendering](/blog/react-pdf-setup-basic-rendering.md)
- [React Pdf Text Layer Custom Renderer](/blog/react-pdf-text-layer-custom-renderer.md)
- [React Pdf Thumbnails Page Navigation](/blog/react-pdf-thumbnails-page-navigation.md)
- [Reducto Alternatives](/blog/reducto-alternatives.md)
- [Requisition System](/blog/requisition-system.md)
- [labels.py](/blog/route-documents-automatically-classify-api.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [System Of Record Vs Source Of Truth](/blog/system-of-record-vs-source-of-truth.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [The Six Best Pdf Generator Apis](/blog/the-six-best-pdf-generator-apis.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Business Logic](/blog/what-is-business-logic.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Ocr Invoice Processing](/blog/what-is-ocr-invoice-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

