Extract tables from a PDF to JSON or CSV — without letting an AI invent cells
The short answer: POST /api/v1/extract/tables with a PDF returns every recoverable table as arrays-of-arrays plus a CSV rendering, each tagged with the page it came from. It's deterministic — built from the PDF's actual positioned text, with no model guessing at cells — and it costs 1 credit per 20 pages.
Here's why "just ask an LLM" is the wrong default for tables, how the endpoint works, and what to do about scanned pages.
The problem with tables is that errors are silent
Tables are where PDF extraction quietly goes wrong. Merged cells, multi-line headers, borderless whitespace-aligned grids, totals rows that span columns — a model reading the page will usually reconstruct them correctly, and when it doesn't, the output still looks plausible. A shifted column in an invoice table doesn't crash anything. It just books the wrong number.
The 2026 crop of AI document tools leans hard into multimodal models for exactly this, and they're genuinely good. But if you're feeding downstream systems — reconciliation, analytics, an agent that acts on the numbers — "usually right, always confident" is the worst possible contract.
The deterministic-first contract
A digital PDF already contains every cell's text at exact coordinates. Inkrun recovers the grid from that positioned text directly:
- A table whose structure can be recovered comes back as data — row-major
rows(arrays of cell strings) plus a ready-madecsv, tagged{ page, index }so every table is traceable to where it appeared. - A table whose grid can't be recovered (borderless merged cells, wrapped multi-line rows) produces a warning naming its page — not a half-guessed grid.
- Ruled tables and whitespace-aligned tables both work. Nothing is invented; run it twice, get the same answer twice.
That last property is the one LLM-only extraction can't offer: the failure mode is a visible warning you can route to a human, instead of a plausible wrong number you find at month-end.
The call
curl -s https://inkrun.dev/api/v1/extract/tables \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{ "file_url": "https://example.com/q2-report.pdf" }'
{
"tables": [
{
"page": 3,
"index": 0,
"rows": [
["Region", "Q1", "Q2"],
["EMEA", "412,090", "455,310"],
["Americas", "1,204,551", "1,310,002"]
],
"csv": "Region,Q1,Q2\nEMEA,\"412,090\",\"455,310\"\n…"
}
],
"warnings": [],
"pages": 12,
"credits_charged": 1
}
rows is what your code wants; csv is what your spreadsheet wants — paste it straight into Excel or Google Sheets, or write it to a file. There's no preset or schema to configure: the endpoint always returns all recoverable tables. (If you want tables alongside named fields — vendor, total, due date — use POST /api/v1/extract with the table_dump preset instead; it returns the same tables inside the full extraction envelope.)
Scanned pages: OCR is opt-in, never a surprise
A scanned page has no positioned text at all, so the deterministic pass yields no tables from it — and tells you so. Before spending anything, call POST /api/v1/inspect (free): it reports the page count, which pages are scans, and exactly what each tier would cost.
If inspect shows scanned pages, add "ocr_fallback": true to read their tables off the page image. OCR is the expensive tier and it never runs unless you ask — a scanned document without the flag returns cheap partial results plus a warning telling you what OCR would recover and cost. Because OCR reads pixels rather than text, its output can't be verified against a text layer; Inkrun labels it accordingly instead of blending it invisibly with grounded data. Current tier prices live on the pricing page.
From an agent, it's one tool call
The same capability ships on Inkrun's MCP server as extract_tables (connector setup). Attach a PDF in Claude and ask:
"Pull every table out of this report and give me the Q2 revenue table as CSV."
The agent gets real rows with page provenance — not a model's memory of what the table said — which makes everything it does next auditable. And because extraction and rendering share one API and one credit meter, the same agent can turn those tables back into a styled PDF summary in the next tool call.
How this compares to the usual suspects
- Camelot / Tabula (open source): same deterministic philosophy, and genuinely good — if you want to host, tune, and maintain them yourself. Inkrun is the hosted version of that contract, with OCR fallback, provenance, and billing that an agent can reason about.
- Cloud OCR suites (Textract, Document AI, Azure): powerful, but OCR-first — you pay the vision tier even for born-digital PDFs, and pricing/setup are built for enterprise pipelines, not a single API call.
- LLM/multimodal parsers: the right tool when semantic reconstruction matters more than fidelity (research, RAG ingestion). The wrong default when a shifted column costs money.
FAQ
What does it cost? Deterministic table extraction is 1 credit per 20 pages — and inspection is always free, so you can price a document before touching it. The free tier's monthly credits cover both extraction and rendering.
Is my document stored? No. Documents are processed in memory and never stored — page images included. Only what you explicitly render is kept.
PDF to Excel, specifically? Take the csv field and open it in Excel, or feed rows to your spreadsheet library of choice. The point of returning both is that you never re-parse.
What about tables split across pages? Each page's recoverable grid is returned as its own table with its page tag; joining continuations is a one-liner once you can see where each fragment came from.