"Which OCR should I use?" is usually the wrong question.
If you're:
- automating invoice or form processing,
- building a RAG pipeline from PDFs,
- or setting up a compliance workflow that needs traceable data,
the question that actually matters is: do you need text, or do you need structure?
The goal isn't just to make a document readable. The goal is to make document data usable, reviewable, and ready for downstream systems.
TL;DR
OCR answers "what does this text say?" — it converts pixels into characters.
AI document extraction answers "what is this content, where is it on the page, and how does it relate to the rest of the document?" — it preserves structure like tables, fields, reading order, and source context.
Nutrient Data Extraction API exposes this as four processing modes — Text, Structure, Understand, and Agentic — so you can match extraction depth to document complexity.
Use Parse when you need full document structure as Markdown or spatial JSON. Use Extract when you need specific schema-defined fields returned as structured JSON with source context for review.
Below: the same document requested in two output formats, so you can see exactly where plain text extraction stops and structure begins.
Why "OCR" became the wrong catch-all term
Most people use "OCR" to mean "get text out of an image." That's technically correct and practically misleading.
Plain OCR usually gives you recognized text, sometimes with word or line positions. But it does not reliably preserve higher-level structure like tables, field relationships, heading hierarchy, or reading order across complex layouts. Some OCR engines return positions or basic layout hints, but that's not the same as understanding how a page is organized.
Then teams plug that output into a downstream system expecting structure — and discover the table became "Invoice #2024-0892\nItem Amount\nWidget A $45.00", with no indication of which value belongs to which column.
That's not an OCR bug. It's OCR doing exactly what it was built to do.
The real difference: reading vs understanding
OCR reads. It converts pixels to characters, in roughly left-to-right, top-to-bottom order, and stops there.
Document understanding — the job behind AI-powered document extraction — analyzes layout. It detects tables and preserves rows and columns. It recognizes reading order across multi-column pages. It classifies elements (paragraph, heading, table, key-value region) instead of just returning a wall of text.
The distinction that matters in practice: plain text extraction gives you the words. Structured extraction gives you the words with layout, relationships, and context
Four modes, one spectrum
Nutrient Data Extraction API makes this spectrum concrete with four processing modes, all reachable through the same API:
| Capability | Text | Structure | Understand | Agentic |
|---|---|---|---|---|
| Text extraction | Yes | Yes | Yes | Yes |
| Table structure | No | Yes | Yes | Yes |
| Key-value regions | No | Yes | Yes | Yes |
| Handwriting | No | Limited | Yes | Yes |
| Formulas | No | No | Yes, as LaTeX | Yes, as LaTeX |
| Reading order | Basic | Layout-aware | Layout-aware | Layout-aware, deeper reasoning |
| Best fit for | Fast Markdown/text extraction for born-digital documents | Spatial JSON and OCR-backed layout for scans and image-based documents | Complex layouts, tables, handwriting, formulas, and richer structure | The most complex documents that need deeper visual reasoning and recovery |
| Relative cost/speed | Fastest, cheapest | Moderate | Higher | Highest, slowest |
Text mode — Fast text extraction for born-digital documents.
Structure mode — OCR-backed structure extraction for scans and image-based documents.
Understand mode — ICR for complex layouts, handwriting, formulas, OCR correction, and richer document structure.
Agentic mode — VLM-enhanced ICR for deeper visual reasoning, semantic understanding, and recovery.
Under the hood, components like OCR and ICR (intelligent character recognition) do the character-level work, but the mode you choose is what actually changes what comes back.
Studio-first, API-next
Before deciding which mode fits a document, Data Extraction API Studio lets you upload a file, run it through Parse, compare modes side by side, and inspect the Markdown or JSON output directly in your browser — 5,000 free credits, no credit card required. It's the fastest way to see the difference on your own documents before writing any integration code.
Parse vs. Extract, and Markdown vs. JSON
There are two separate choices that shape what comes back from the API, and it's worth naming both before writing any code: what job you want the API to perform, and what output format your workflow needs.
Use Parse when you want the full document structure. The Markdown response is clean, readable content for AI, RAG, search, and document Q&A. It is easier to work with than raw OCR text, but it is not the best format when you need layout-aware elements, coordinates, confidence, and table cell relationships.
Use Extract when you need specific schema-defined fields — invoice number, vendor, total, line items, contract dates, form values — returned as structured JSON with source context for review. Studio's schema generator can scaffold a starting schema from a sample document, which you can review and refine before running extraction.
The processing mode (Text/Structure/Understand/Agentic) determines how deeply the document is analyzed. The output format determines how the result is handed back to you.
Implementation: the same document, two output formats
Here's the same intake form requested in two output formats — Markdown for clean, readable content, and JSON for layout-aware structure — using the REST API:
curl -X POST https://api.nutrient.io/extraction/parse \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "file=@intake_form.pdf" \
-F 'instructions={"output":{"format":"markdown"}}'
curl -X POST https://api.nutrient.io/extraction/parse \
-H "Authorization: Bearer $NUTRIENT_API_KEY" \
-F "file=@intake_form.pdf" \
-F 'instructions={"output":{"format":"json"}}'
Same file, same endpoint. The processing mode is set as a separate request parameter alongside the output format — check the API documentation for the current field name and accepted values, or set it directly in Studio and copy the generated request.
The Markdown response is a clean, flat rendering of the content — accurate, but with table rows collapsed into plain text. A simplified spatial JSON response might look like this:
{
"elements": [
{
"type": "table",
"boundingBox": { "x": 45, "y": 200, "width": 680, "height": 340 },
"readingOrder": 1,
"children": [
{ "type": "tableCell", "text": "Item", "row": 0, "column": 0 },
{ "type": "tableCell", "text": "Amount", "row": 0, "column": 1 }
]
}
]
}
That nesting is the entire difference. Plain text extraction never produces it — not because it's less accurate, but because table structure isn't something character recognition alone is designed to detect.
Scaling up: processing 100 invoices
A single curl call is enough to see the difference between output formats. It's not enough to run a real batch. Here's a simple Python script that walks a folder of invoices, sends each one through the API, and saves the results — the kind of script you'd actually point at 100 files:
"""
Batch-process a folder of invoices through the Nutrient Data Extraction API.
Usage:
python batch_extract.py ./invoices --format json --out ./results
"""
import argparse
import json
import time
from pathlib import Path
# Note:
# The response field names below (for example "output" and
# "usage.data_extraction_credits") are illustrative.
# Verify the current response schema in the official API
# documentation before using this script in production.
import requests
API_KEY = "your_api_key"
URL = "https://api.nutrient.io/extraction/parse"
def extract_one(pdf_path: Path, output_format: str) -> dict:
with open(pdf_path, "rb") as f:
response = requests.post(
URL,
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": f},
data={"instructions": f'{{"output":{{"format":"{output_format}"}}}}'},
timeout=60,
)
response.raise_for_status()
return response.json()
def main():
parser = argparse.ArgumentParser(description="Batch extract invoices with Nutrient")
parser.add_argument("folder", help="Folder containing invoice PDFs")
parser.add_argument("--format", choices=["markdown", "json"], default="json")
parser.add_argument("--out", default="./results", help="Folder to write results to")
args = parser.parse_args()
input_dir = Path(args.folder)
output_dir = Path(args.out)
output_dir.mkdir(parents=True, exist_ok=True)
pdf_files = sorted(input_dir.glob("*.pdf"))
print(f"Found {len(pdf_files)} invoices")
failed = []
total_credits = 0
for i, pdf_path in enumerate(pdf_files, start=1):
print(f"[{i}/{len(pdf_files)}] {pdf_path.name}")
try:
result = extract_one(pdf_path, args.format)
except requests.HTTPError as e:
print(f" failed: {e}")
failed.append(pdf_path.name)
continue
# Note: field names below (output, usage.data_extraction_credits) should be
# confirmed against the current API reference before running this in production.
out_path = output_dir / f"{pdf_path.stem}.{('json' if args.format == 'json' else 'md')}"
content = result["output"].get(args.format, result["output"])
if isinstance(content, dict):
content = json.dumps(content, indent=2)
out_path.write_text(content, encoding="utf-8")
credits = result.get("usage", {}).get("data_extraction_credits", {})
total_credits += credits.get("cost", 0)
# Simple pacing to stay under rate limits on large batches
time.sleep(0.2)
print(f"\nDone. {len(pdf_files) - len(failed)} succeeded, {len(failed)} failed.")
print(f"Total credits used: {total_credits}")
if failed:
print("Failed files:", ", ".join(failed))
if __name__ == "__main__":
main()
Run it: python batch_extract.py ./invoices --format json --out ./results.
A few things worth calling out for a batch like this:
- Failures are tracked, not fatal. One malformed PDF in a folder of 100 shouldn't kill the run — the script logs it and keeps going, then prints the failed filenames at the end so you can retry just those.
- Credit usage is summed across the batch, so you know the actual cost of processing 100 invoices before you scale to 1,000.
- The
time.sleep(0.2)is a simple pacing guard, not a rate-limit implementation — for larger batches or production use, check the API docs for current rate limits and consider proper backoff instead of a fixed delay. - Structure or Understand mode is usually the better starting point for real invoices, especially when you need tables, line items, and totals
When each one is the wrong tool
Text mode is the wrong tool when: you need to reconstruct a table, route a form field to the correct database column, or trust that "Total" and "$4,250" are actually linked. It will hand you the right characters in the wrong shape.
Understand or Agentic mode is the wrong tool when: you're indexing millions of simple, clean digital pages for keyword search and don't need layout at all. Running the deepest mode on documents that Text mode would handle just as well is the most common way teams overpay for extraction they don't need.
Match the mode to the job — not the other way around. If you need specific fields rather than the whole document, Extract with a schema (generated from a sample document via Studio's schema generator) is usually a better fit than parsing the full page and post-processing it yourself.
If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.
Key takeaways
- Plain OCR converts pixels to characters. Plain text extraction gets text out, but it was never meant to understand layout.
- Structure, Understand, and Agentic modes classify elements, preserve table structure, and determine reading order — a different job, not a better version of the same job.
- Picking the right mode per document (not defaulting to the most powerful one) is what keeps extraction both accurate and cost-efficient.
FAQs
❓ Is AI document extraction just "OCR with AI added"?
✅ Not exactly. OCR is the character-recognition layer. AI document extraction uses OCR or other recognition methods as part of a broader pipeline that also analyzes layout, detects tables, identifies fields, preserves reading order, and returns structured output.
❓ Do I need to run OCR separately before using Structure or Understand mode?
✅ No — both modes already include character recognition as part of the pipeline. You don't run OCR first and then request structure on top; you pick the mode that matches what the document needs.
❓ Can Text mode handle scanned tables if I clean up the output myself?
✅ You can write custom logic to reconstruct a table from flat text, but it breaks the moment a vendor changes their layout. Structure mode is designed to reduce this brittleness by returning layout-aware elements, table structure, coordinates, and page context.
❓ Which mode should I default to if I'm not sure?
✅ Start with Structure mode for scanned or image-based documents. Move up to Understand or Agentic when tables, columns, handwriting, or complex layouts need deeper analysis
❓ Does a more advanced mode always mean better results?
✅ Not for every document. Understand and Agentic modes cost more and run slower than Structure — worth it for genuinely complex layouts, unnecessary overhead for a clean, well-structured scan.
Nutrient Data Extraction API
Turn PDFs, scans, images, and Office files into structured JSON or clean Markdown — with the right mode for every document.
→ Get started free
Top comments (0)