Documentation
Extraction · Extract
Pull structured data out of a PDF. Every returned value was found at a concrete position in the document — a field that can't be grounded comes back null with a warning, never a guess.
- Endpoint:
POST /api/v1/render's sibling —POST https://inkrun.dev/api/v1/extract, authenticated with the sameAuthorization: Bearer sk_...key. - MCP tool:
extract— same contract, same envelope, available to any agent connected to the Inkrun MCP server. - Recommended workflow: call Inspect first — it's free and tells you the page count, which pages are scanned, and exactly what each tier will cost for your document.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
file_base64 | string | one of | Standard base64 of the PDF bytes. (file_path works only on the local stdio MCP server, where the file is read on your machine.) |
file_url | string | one of | An http(s) URL the server downloads the PDF from — cheaper than base64 for agents, which would otherwise emit the whole file as tokens. Must be publicly reachable: private, loopback and link-local addresses are refused, redirects are capped, and the download is size-capped to your plan’s document limit. |
preset | string | one of the two | A built-in extraction preset: invoice, receipt, contract_terms, table_dump, full_text. Mutually exclusive with schema. |
schema | object | one of the two | A JSON Schema object describing the fields you want. Mutually exclusive with preset — send exactly one of the two; both, neither, or an unknown preset name is a 400 invalid_request. |
ocr_fallback | boolean | no | Opt in to the OCR tier for pages with no text layer. Defaults to false — an absent flag can never escalate to (or be charged for) the expensive tier. See Extraction tiers. |
mime | string | no | Content type hint for the document; PDFs are detected without it. |
Example — preset
curl "https://inkrun.dev/api/v1/extract" \
-H "Authorization: Bearer $INKRUN_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"file_base64\": \"$(base64 -i invoice.pdf)\",
\"preset\": \"invoice\"
}"Example — custom schema
curl "https://inkrun.dev/api/v1/extract" \
-H "Authorization: Bearer $INKRUN_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"file_base64\": \"$(base64 -i order.pdf)\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"customer\": { \"type\": \"string\" },
\"order_date\": { \"type\": \"string\", \"format\": \"date\" },
\"total\": { \"type\": \"number\" }
}
}
}"Custom schemas are plain object schemas: $ref, allOf/anyOf/oneOf and other composition keywords are rejected by name, and size, depth and property count are capped (400 schema_too_complex). Field names double as label anchors in the document, so name them the way the document labels them — invoice_number, due_date, total. String fields with format: "date" parse as dates; number fields named total/amount/price parse as money.
Response — the envelope
{
"id": "ext_9f2a4e1c",
"data": {
"vendor": "Acme Co",
"invoice_number": "INV-2026-0042",
"invoice_date": "2026-06-30",
"due_date": null,
"currency": "USD",
"subtotal": 1130.00,
"tax": 110.50,
"total": 1240.50,
"line_items": [
{ "description": "Consulting", "quantity": 10, "unit_price": 113.00, "amount": 1130.00 }
]
},
"fields": {
"vendor": { "confidence": 0.98, "method": "deterministic", "page": 1 },
"invoice_number": { "confidence": 0.99, "method": "deterministic", "page": 1 },
"invoice_date": { "confidence": 0.97, "method": "deterministic", "page": 1 },
"currency": { "confidence": 0.95, "method": "deterministic", "page": 1 },
"subtotal": { "confidence": 0.98, "method": "deterministic", "page": 2 },
"tax": { "confidence": 0.98, "method": "deterministic", "page": 2 },
"total": { "confidence": 0.99, "method": "deterministic", "page": 2 },
"line_items": { "confidence": 0.93, "method": "deterministic", "page": 1 }
},
"extraction_method": "deterministic",
"warnings": [
"due_date could not be grounded in the document and was returned as null."
],
"credits_charged": 1,
"pages": 2,
"size_bytes": 184220,
"duration_ms": 412
}| Field | Description |
|---|---|
id | Extraction id, echoed in logs and support requests. |
data | Schema-shaped output. A field that couldn't be grounded is null — never a guess. |
fields | Per-leaf provenance for every populated leaf of data: confidence (0–1, grounded — never model-self-reported), method (the tier that produced the value) and page (where it was found). |
extraction_method | The highest tier used for the envelope: deterministic, model, or ocr. |
warnings | One entry per ungrounded field, plus notices such as scanned pages and what OCR would cost. |
credits_charged | What this call actually cost — you are only ever charged for the tier that ran. |
pages | Page count of the document. |
size_bytes | Decoded document size. |
duration_ms | Server-side processing time. |
Presets
A preset ships its own output schema and grounding rules — the fastest path for the common document types:
| Preset | Returns |
|---|---|
invoice | vendor, invoice_number, invoice_date, due_date, currency, subtotal, tax, total, line_items[] (description, quantity, unit price, amount). |
receipt | merchant, date, total, currency, payment_method, items[]. |
contract_terms | party_1, party_2, effective_date, initial_term, auto_renews, termination_notice_days, governing_law. |
table_dump | tables[] — every recoverable table as { page, index, rows, csv }; the same output as Extract tables, inside the extract envelope. |
full_text | text plus per-page text — the document's text layer in reading order. |
Extraction tiers & credits
Extraction escalates through up to three tiers, and you are only charged for the tier that actually ran:
- Deterministic — positioned-text rules over the document's text layer. Reproducible run-to-run, never leaves Inkrun's servers.
- Model — fields the rules couldn't ground are escalated to a model over the extracted text and layout (never your file bytes); returned values are verified against the document text before they're trusted.
- OCR — opt-in only (
ocr_fallback: true), for scanned pages with no text layer. Because OCR reads pixels, its values can't be verified against extracted text: they are labelledmethod: "ocr"and their confidence is capped at 0.8. Refused above 15 scanned pages in one call (413).
| Operation | Cost | Past the included pages | Notes |
|---|---|---|---|
| Inspect | Free | — | Rate-limited, never billed |
| Deterministic extraction | 1 credit (up to 20 pages) | +1 credit per started 20 pages | Positioned-text rules — the default tier |
| Model extraction | 3 credits (up to 5 pages) | +1 credit per started 5 pages | Text-grounded model escalation |
| OCR extraction | 10 credits (up to 5 pages) | +2 credits per started 5 pages | Opt-in, for scanned pages only |
| PDF render | 1 credit | — | The historical unit — 1 render = 1 credit |
Example: a 30-page digital invoice costs 2 credits on the deterministic tier (1 for the first 20 pages, +1 for the started next 20). A 5-page scan with ocr_fallback: true costs 10. Over-quota fails before any parsing with 429 quota_exceeded.
Errors
Same envelope as the render API: { "error": { "code", "message" } }, with x-request-id echoed on every response.
| Status | code | When |
|---|---|---|
400 | invalid_request | Preset XOR schema violated, unknown preset, or a malformed body — the message teaches the contract. |
400 | schema_too_complex | Custom schema over the size/depth/property caps or using composition keywords. |
413 | input_too_large | Document over your plan's size limit, too many pages, or more scanned pages than one OCR call may carry. |
415 | unsupported_document | Not a document Inkrun can parse (or it crashed the parser). |
422 | document_encrypted | Password-protected PDF. |
429 | quota_exceeded | Monthly credit quota exhausted — checked before any parsing. |
429 | rate_limited | Burst rate limit tripped (+ Retry-After). |
503 | pool_busy | Extraction pool saturated (+ Retry-After: 5). |
504 | render_timeout | Per-document parse time limit reached — try a smaller or simpler document. |
500 | internal_error | Unexpected failure — quote the x-request-id when contacting support. |
Machine-readable spec
This page is the human reference. A machine-readable OpenAPI spec for the v1 API (render and extraction together) is planned as its own piece of work and will be cross-linked here when it ships.