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 same Authorization: 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)

FieldTypeRequiredDescription
file_base64stringone ofStandard base64 of the PDF bytes. (file_path works only on the local stdio MCP server, where the file is read on your machine.)
file_urlstringone ofAn 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.
presetstringone of the twoA built-in extraction preset: invoice, receipt, contract_terms, table_dump, full_text. Mutually exclusive with schema.
schemaobjectone of the twoA 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_fallbackbooleannoOpt 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.
mimestringnoContent type hint for the document; PDFs are detected without it.

Example — preset

bash
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

bash
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

json
{
  "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
}
FieldDescription
idExtraction id, echoed in logs and support requests.
dataSchema-shaped output. A field that couldn't be grounded is null — never a guess.
fieldsPer-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_methodThe highest tier used for the envelope: deterministic, model, or ocr.
warningsOne entry per ungrounded field, plus notices such as scanned pages and what OCR would cost.
credits_chargedWhat this call actually cost — you are only ever charged for the tier that ran.
pagesPage count of the document.
size_bytesDecoded document size.
duration_msServer-side processing time.

Presets

A preset ships its own output schema and grounding rules — the fastest path for the common document types:

PresetReturns
invoicevendor, invoice_number, invoice_date, due_date, currency, subtotal, tax, total, line_items[] (description, quantity, unit price, amount).
receiptmerchant, date, total, currency, payment_method, items[].
contract_termsparty_1, party_2, effective_date, initial_term, auto_renews, termination_notice_days, governing_law.
table_dumptables[] — every recoverable table as { page, index, rows, csv }; the same output as Extract tables, inside the extract envelope.
full_texttext 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 labelled method: "ocr" and their confidence is capped at 0.8. Refused above 15 scanned pages in one call (413).
OperationCostPast the included pagesNotes
InspectFreeRate-limited, never billed
Deterministic extraction1 credit (up to 20 pages)+1 credit per started 20 pagesPositioned-text rules — the default tier
Model extraction3 credits (up to 5 pages)+1 credit per started 5 pagesText-grounded model escalation
OCR extraction10 credits (up to 5 pages)+2 credits per started 5 pagesOpt-in, for scanned pages only
PDF render1 creditThe 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.

Your document is processed in memory and never stored — page images included. See Data handling for the full posture, including exactly what the model and OCR tiers send upstream.

Errors

Same envelope as the render API: { "error": { "code", "message" } }, with x-request-id echoed on every response.

StatuscodeWhen
400invalid_requestPreset XOR schema violated, unknown preset, or a malformed body — the message teaches the contract.
400schema_too_complexCustom schema over the size/depth/property caps or using composition keywords.
413input_too_largeDocument over your plan's size limit, too many pages, or more scanned pages than one OCR call may carry.
415unsupported_documentNot a document Inkrun can parse (or it crashed the parser).
422document_encryptedPassword-protected PDF.
429quota_exceededMonthly credit quota exhausted — checked before any parsing.
429rate_limitedBurst rate limit tripped (+ Retry-After).
503pool_busyExtraction pool saturated (+ Retry-After: 5).
504render_timeoutPer-document parse time limit reached — try a smaller or simpler document.
500internal_errorUnexpected 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.