Government PDF Table Extractor – PDF to JSON
Pricing
from $8.50 / 1,000 table extracteds
Government PDF Table Extractor – PDF to JSON
PDF table extractor for government reports and annual accounts: extract tables from PDF to JSON with headers, typed records, page and position, confidence and sum checks. Joins multi-page tables, handles merged headers and Dutch number formats; Claude can read scanned pages, validated in code.
Pricing
from $8.50 / 1,000 table extracteds
Rating
0.0
(0)
Developer
Leyten Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a month ago
Last modified
Categories
Security
Limited permissionsShare
What does Government PDF Table Extractor do?
PDF table extractor for government reports, annual accounts and statistics: give it PDF URLs (budgets, annual reports, key-figure publications, price lists) and get every table back as clean JSON rows with the column headers, typed records, the page and position it came from, a confidence score and the checks that were run on it. Tables that continue over a page break come back as one table. A deterministic pass reads PDFs that have a text layer, with no model and no API cost; an optional LLM pass (Claude) reads scanned or messy pages, and everything the model returns is validated in code before you see it.
Why tables from PDFs are hard, and what this Actor does about it
| Problem | What happens here |
|---|---|
| Column headers spread over two or three lines, with a group label ("Jaarrekening", "Begroting") centred above several year columns | Header rows are joined into one name per column and group labels are spread over the columns they cover: Jaarrekening 2020, Begroting 2025** |
| Borderless tables aligned by whitespace, and ruled tables with thin lines | On Detection auto, three readers run on every page with a text layer (Camelot stream for whitespace, Camelot lattice and pdfplumber for ruled lines) and the best reading per table region is kept; stream runs Camelot stream alone, lattice the two ruled-line readers |
| A table continues on the next page, with or without the header printed again | Continuations with the same columns are joined into one table with pages: [14, 15] and merged: true; the repeated header is dropped |
| Row labels that wrap onto two lines, and words cut in two at a column boundary | Re-joined |
| Section headings inside the table ("Mobiliteit in Nederland") | Kept as rows, listed in sectionRows, excluded from column typing and sums |
Dutch and English number formats (4.585 vs 4,585.00), negatives in parentheses, footnote marks on years | The format is decided per column, never per cell; records carry real numbers |
| Totals | Every row labelled Totaal/Total/Som is checked against the rows above or below it, at the precision the table prints; a mismatch is reported in checks, never corrected |
| Text boxes and chart legends that look like two-column tables | Dropped (prose filter, chart captions) |
| Scanned PDFs, faint scans, exotic layouts | llm-fallback sends only those pages to Claude; llm sends every page. The model transcribes, the code validates: column counts, numbers, sums, expected columns |
Low confidence means a marking (lowConfidence: true, the reasons in checks and warnings), never a guess filled in for you.
The three modes
fast (default): deterministic only. The readers of your Detection setting read the text layer (on auto: Camelot 2.0 stream and lattice, plus pdfplumber); no model, no API cost. Pages without a text layer (scans) are reported in the document record and yield no tables.
llm-fallback: the deterministic pass first; then Claude Haiku 4.5 (or Sonnet 5) reads the pages that have no text layer and the pages whose tables all came out with low confidence, from a rendered image of the page. Its answer goes through the same normalisation and checks as the deterministic result.
llm: Claude reads every page. For documents where layout beats the deterministic detectors (multi-level headers with merged cells, forms, brochures).
The model call logs its tokens and cost per page (LLM page 1 of kerncijfers-mobiliteit-2024-p14.pdf: Claude Haiku 4.5, 1,472 in / 812 out tokens, $0.0055 API cost, 1 table(s)) and the run summary totals them. The LLM events are priced to cover that cost.
How the choice of libraries was made
The deterministic engine is Camelot 2.0 for whitespace-aligned and ruled tables, with pdfplumber as a second reader of ruled lines; both are pinned in the Actor's image. Pages these readers cannot handle, such as scans and unusual layouts, are what the LLM mode is for.
Confidence and checks
confidence starts from the detector's own accuracy and whitespace measures (or the model's stated confidence) and is lowered for: no header row detected, rows with a different number of cells, columns that mix numbers and text, totals that do not add up, columns without a header name, and a column count that differs from Expected columns. Below 0.7 the table is lowConfidence. The checks array says exactly what was tested and what was found, for instance:
sum · ok: false · Totaal: the 5 rows above do not add up to it: "Jaarrekening 2023" adds up to 76,74, the table prints 76,64
A failed sum check means the rows and the total as printed do not agree: either a cell was misread, or the table lists only some of the components. The Actor tells you; it does not decide for you.
Features
- Extract tables from PDF to JSON: headers, rows, typed records, caption and footnotes per table
- Multi-line and grouped headers joined into one name per column
- Multi-page tables merged into one, with the repeated header dropped
- Dutch and English number formats parsed per column into real numbers
- Sum checks on totals and subtotals, reported, never silently corrected
- Confidence score and explicit checks per table; low confidence is a marking, not a guess
- Page, position and optional cell bounding boxes for every table the deterministic pass finds; tables read by Claude carry their page only
- Three readers on Detection auto (Camelot stream, Camelot lattice, pdfplumber lines) with the best reading kept per region; stream uses Camelot stream alone, lattice the two ruled-line readers
- Optional Claude pass for scanned or messy pages, validated in code, with cost logged per page
- Runs through the Apify API, on a schedule, or from Make, Zapier and n8n
Use cases
Data and RAG engineers: tables that survive chunking
Running text goes through chunkers fine; tables do not. Extract them once, store records next to the document with page and caption, and let the retriever return the table as structured data (or a Markdown rendering of headers + rows) instead of a scrambled paragraph.
// npm install apify-clientimport { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('lwsdjfls/govt-pdf-table-extractor').call({pdfUrls: ['https://open.overheid.nl/documenten/0ceeb3e8-e2b6-4237-9772-f75087e320ed/file'],mode: 'fast',});const { items } = await client.dataset(run.defaultDatasetId).listItems({ clean: true });for (const { payload } of items) {if (payload.kind !== 'table') continue;if (payload.lowConfidence) console.warn(`${payload.fileName} p.${payload.page}: check ${payload.checks.filter((c) => !c.ok).map((c) => c.detail).join('; ')}`);await store.saveTable({ document: payload.documentId, page: payload.page, caption: payload.caption, headers: payload.headers, records: payload.records });}
Policy analysts: government and statistical data
Budgets, annual reports and key-figure publications on open.overheid.nl, rijksfinancien.nl and CBS come as PDFs with the numbers in tables. The example below is Table 1 of the Integraal Overzicht Financiën Gemeenten 2025 (open.overheid.nl): the key financial figures of all municipalities 2020–2024, delivered as numbers. The same document's Table 2 (income per source, with the group headers Jaarrekening and Begroting spread over the year columns) comes out with every subtotal verified and one finding: the printed grand total for 2023 is 0,10 off the sum of its parts.
Finance and procurement teams
Price lists, statements and supplier reports: set Expected columns to the columns your import needs; tables that match get those names as record keys, tables that do not are flagged in checks and marked low confidence so nobody imports the wrong grid.
Journalists and researchers
Point it at a folder of reports (URLs), take the tables view, and filter on caption.
How to use it
- Paste the document links into PDF URLs, or post the files themselves as base64 in PDF files if they are not public. Narrow to the pages that matter with Pages, written like
14-17or1-5,8. Pages you do not ask for are never read and never charged. - Leave Mode on
fastfirst. That is the deterministic reader: it costs the least and, for any PDF with a real text layer, it is also the most accurate. Only if pages come back with low confidence, or with no text layer at all, should you move tollm-fallback, which sends exactly those pages to Claude and leaves the rest alone. A page that has a text layer but yielded no table is not sent;llmis for those. Reservellmfor documents that are scans from end to end. - Read the
tablesview for the extracted tables anddocumentsfor what happened per file. Every table carries its page, its position, the headers, the confidence and the sum checks, so you can tell a clean extraction from one worth a human glance instead of trusting all of them equally.
Set Expected columns when you process the same report every quarter. Tables that match the count get your column names as record keys, and any table that does not is flagged rather than quietly renamed. Join tables that continue on the next page keeps a table that runs over a page break as one table with all its rows.
Nothing is silently accepted. Whatever the model reads on a scanned page goes through the same checks in code as the deterministic result (column counts, numbers, sums, expected columns) before it is delivered, and a file that cannot be fetched or is larger than Maximum file size comes back as a failed document record that costs nothing, rather than disappearing from your results.
{"pdfUrls": ["https://open.overheid.nl/documenten/dpc-cee2e3debdd8cd3e16f84a876ee24537e3e034c0/pdf"],"pages": "14-17","mode": "fast"}
Input
| Field | What it does | Example |
|---|---|---|
pdfUrls | Direct http(s) links to PDF files, one per line, each up to 2,048 characters; links to key-value store records work too. A link listed twice is read once. | ["https://open.overheid.nl/documenten/dpc-cee2e3debdd8cd3e16f84a876ee24537e3e034c0/pdf"] |
pdfFiles | For API callers: base64 files, up to 100, within Apify's input size limit. | [{ "fileName": "report.pdf", "data": "<base64>" }] |
pages | Page numbers and ranges to read, for every document, such as 1-5, 8 (up to 1,000 characters); empty means all pages. | "14-17" |
mode | fast (deterministic only), llm-fallback (Claude for scans and low-confidence pages) or llm (Claude reads every page). | "fast" |
llmModel | haiku (Claude Haiku 4.5, cheaper) or sonnet (Claude Sonnet 5, for dense or hard layouts). | "haiku" |
flavor | auto, stream (whitespace only) or lattice (ruled lines only). | "auto" |
expectedColumns | The column names you expect, in order; matching tables get them as record keys, others are flagged. | ["Post", "2023", "2024"] |
minRows | Tables with fewer body rows are ignored. | 2 |
minColumns | Tables with fewer columns are ignored. | 2 |
mergeAcrossPages | Join a table that continues on the next page. | true |
includeCells | Add every cell with its page and bounding box to each table. | false |
maxPagesPerDocument | Pages beyond this are not read or charged. | 200 |
maxFileMb | Larger files are reported as failed and cost nothing; at most 100. | 50 |
maxLlmPagesPerRun | Caps the pages sent to Claude in one run. | 200 |
proxyConfiguration | Only for downloading PDFs from sites that block data-centre addresses. | { "useApifyProxy": false } |
{"pdfUrls": ["https://open.overheid.nl/documenten/dpc-cee2e3debdd8cd3e16f84a876ee24537e3e034c0/pdf"],"pages": "14-17","mode": "llm-fallback","llmModel": "haiku","mergeAcrossPages": true}
For files you hold yourself, pass them base64-encoded in pdfFiles, or upload them to a key-value store and give the record URL in pdfUrls.
Supported input and limits
- PDF files with a text layer (any producer); scanned or image-only PDFs in the LLM modes. Password-protected PDFs are not supported.
- Up to 500 URLs or 100 base64 files per run, 50 MB per file by default (100 MB at most), 200 pages per document by default.
- The same file twice (a URL listed twice, two URLs that serve identical bytes, or an upload of a file you also linked) is read and charged once; the repeat gets a document record with status
duplicate. - After 20 documents in one run that cost nothing (not downloadable, not a PDF or too large, a repeat of a file already read, a PDF that cannot be opened, tables or a worker answer too large for the run's memory, or no page to process), the remaining documents are not tried; they are reported as failed, free of charge, and
RUN_SUMMARYcounts them underleftOut(the run ispartial). The same happens once the run's spending limit is reached. - A run delivers its tables after its last document, so a run the platform stopped for its timeout while it was still reading would deliver nothing. The run therefore keeps free, before its timeout less a margin of at most 2 minutes to finish, the time its delivery takes: 100 ms for every record, counting the tables read so far, the document being read and one record for each document after it. A document starts only while at least 10 seconds are left beyond that, and what it does ends in the time left: near the timeout each download attempt gets the time left when it starts, when that is less than its usual 2 minutes (at least 10 seconds), and a failed attempt is retried after its usual pause while at least 10 seconds are left; the table readers are stopped when the time is used up; and no page goes to Claude with less than 10 seconds left or waits for its answer beyond the time left. Within these margins, a run whose documents fit in its timeout is read and delivered exactly as it would be without one.
- When the time is used up, the document being read and the documents after it are reported as failed, free of charge, with an
errorthat asks you to submit them again; the log says how many, and the tables already read are delivered. The run ends as succeeded, aspartialinRUN_SUMMARY, which counts those documents underleftOut. A document of which Claude has read pages is delivered all the same, with the pages it read (charged) and the deterministic result for the others, as over Maximum LLM pages per run; a document in an LLM mode of which the time limit kept every page from Claude is reported as failed like the others, free of charge. A download the time limit cut short (an attempt with less than its usual 2 minutes ran out of time, or a retry could not start in time) counts as one of those documents, and itserrorgives the download error and says that it had less time than usual. The records of documents that are not read take at most a minute to deliver (600 documents at most), so a timeout of 5 minutes or more always leaves room for them; with a much shorter timeout and a long list, the run can reach its timeout while it delivers them, and the records it had not delivered by then are missing from the dataset. - The default run timeout is 24 hours, the longest default this Actor sets. The largest batch the input allows (600 documents of up to 2,000 pages, with up to 10 minutes of table reading per document) can need more; split such a batch over several runs. A caller can set another timeout in the run options.
- In the LLM modes the model's answer is capped at 4,096 output tokens per page. A page with more table text than that can come back incomplete, and the log says so.
- A run stops sending pages to Claude when its API cost climbs far above what its LLM pages are charged (a safeguard against pages that cost more to read than their price); the remaining pages are delivered from the deterministic pass.
- Tables drawn as images inside a text-layer PDF are not read in fast mode (the page has text, so it is not treated as a scan); use
llm-fallbackwith a low-confidence outcome orllm. - Rotated pages and tables set sideways are read as they are; the detectors do not rotate.
- Nested tables and tables with cells that span rows and columns in irregular ways come back as a flat grid with the spanning text in the first cell of the span.
- Sum checks look at every row whose label starts with Totaal, Total, Subtotaal, Subtotal, Gesamt, Somme or the word Som (so Totale counts too). A table lists at most 100 sum checks and 100 warnings; the rest are counted in a closing entry.
- A table too large for one dataset item (about 8 MB of JSON, such as a table of thousands of rows joined across hundreds of pages) is delivered as consecutive items with the same caption, headers, footnotes and checks, each with a
partfield; it is charged as one table. A table lists at most 100 footnotes of 20,000 characters together; the rest are counted in a closing entry. - A table that cannot be delivered even in parts, which only a crafted file produces (a single row larger than an item, or headers and other repeated fields taking more than half of one), is left out as a whole, and the pages it is on are still charged. The document record counts it in
tablesNotDeliveredand names its page and size inerror. - A run holds its tables in memory until it delivers them: up to 32 MB of table JSON per GB of memory (128 MB with the 4 GB maximum). A document whose tables do not fit in what is left is reported as failed and not charged (LLM pages already read excepted), and its
errorsays when no run with this memory can hold it; once the run holds half of that and a document no longer fits, the remaining documents are not tried. In the LLM modes the tables on the pages the model does not read are measured first: a document they already make too large sends no page to Claude. The PDF worker's answer for one document is limited the same way (up to about 21 MB per GB, several hundred pages of a typical report). Give the run more memory, or read long documents in page ranges. - File names from the server (or from
pdfFiles) are cleaned of control characters and cut to 200 characters. - In fast mode a page without a text layer is still charged as a page ($0.002) and yields no table: a 200-page scan costs $0.40 and returns none. Use
llm-fallbackfor scanned documents. - The table readers get 10 minutes per document, or less near the run's timeout (see above). A document they do not finish in 10 minutes is reported as failed and costs nothing; read it in smaller page ranges with Pages.
Output
One record per table:
caption,headers,rows(cell text,nullfor empty),records(one object per row keyed by header, numbers parsed),footnotescolumnTypes(number,text,mixed,empty),numberFormat(nloren),sectionRowspage,pages(every page the table covers),bboxin PDF points with a top-left origin,pageSize, optionalcellswith the page and a bounding box per cell (for a table joined across pages, the cells of every page)part:null, or for a table delivered in parts itsindex,count,firstRowandtableRowCount;rows,records,sectionRows,cellsandrowCountthen hold that part's rows, and thetablesview showspartbeside each rowmethod:camelot-stream,camelot-lattice,pdfplumber-lines,llm-haikuorllm-sonnetconfidence(0–1),lowConfidence,checks(column-count,numeric-consistency,header,sum,expected-columns),warnings,merged,textLayerllm: model, tokens and API cost when the model read the pagedocumentId,documentUrl,fileName,tableIndex
Plus one document record per file, with status processed, no-tables, failed or duplicate: pages processed, pages without a text layer, pages read by the LLM, tables found, tables not delivered (tablesNotDelivered), low-confidence tables, the LLM API cost, and the error when the file could not be fetched, opened, held in the run's memory or read before the run's timeout (a failed document costs nothing, except LLM pages the model had already read, which its record lists).
Table 1 of the Integraal Overzicht Financiën Gemeenten 2025 (page 3): caption separated, year headers with footnote marks kept, Dutch thousands separators parsed into numbers, and a row of counts with percentages in parentheses reported in warnings rather than silently parsed.
{"envelopeVersion": 1,"id": "caced5b180e76c8d:t1","scope": "doc:caced5b180e76c8d","changeType": "added","sourceUrl": "https://open.overheid.nl/documenten/0ceeb3e8-e2b6-4237-9772-f75087e320ed/file","scrapedAt": "2026-09-15T20:00:00.000Z","firstSeenAt": "2026-09-15T20:00:00.000Z","hash": "71667740daeff13f87b94c1e0a9ea9b9f72cd895a4ee696c1266f8454a907c5f","previousHash": null,"changes": [],"summary": "Added: Integraal Overzicht Financien Gemeenten 2025.pdf p.3 table 1 (Tabel 1. Kengetallen financiële positie): 11×6","diffText": null,"payload": {"kind": "table","documentUrl": "https://open.overheid.nl/documenten/0ceeb3e8-e2b6-4237-9772-f75087e320ed/file","fileName": "Integraal Overzicht Financien Gemeenten 2025.pdf","documentId": "caced5b180e76c8d","page": 3,"pages": [3],"tableIndex": 1,"caption": "Tabel 1. Kengetallen financiële positie","footnotes": [],"headers": ["label","2020","2021","2022","2023*","2024**"],"rows": [["Exploitatiesaldo (€ mln.)","4.585","1.761","3.745","1.644","1.976"],["Eigen vermogen (€ mln.)","34.081","35.875","39.651","41.322","43.346"],["Materiële en immateriële vaste activa (€ mln.)","66.274","68.605","70.900","74.103","78.423"],["Solvabiliteit (%)","37%","38%","40%","40%","39%"],["Netto schuldquote, gecorrigeerd voor doorgeleende gelden (%)","48%","49%","42%","41%","41%"],["Exploitatieruimte (%)","7%","3%","5%","2%","2%"],["Aantal gemeenten met 3 jaar op rij negatief resultaat","69 (20%)","30 (9%)","5 (1%)","9 (3%)","10 (3%)"],["Grondexploitatie (afgezet tegen totale baten)","7%","6%","5%","5%","5%"],["EMU saldo (€ mln.)","-931","426","2.184","-1.223","-2.942"],["Stijging OZB-tarief, gecorrigeerd voor waardeontwikkeling (%)","4,9%","4,7%","3,6%","6%","5,9%"],["Netto investeringsquote (%)","1,0%","1,0%","0,8%","1,4%","1,9%"]],"records": [{"2020": 4585,"2021": 1761,"2022": 3745,"label": "Exploitatiesaldo (€ mln.)","2023*": 1644,"2024**": 1976},{"2020": 34081,"2021": 35875,"2022": 39651,"label": "Eigen vermogen (€ mln.)","2023*": 41322,"2024**": 43346},{"2020": 66274,"2021": 68605,"2022": 70900,"label": "Materiële en immateriële vaste activa (€ mln.)","2023*": 74103,"2024**": 78423},{"2020": 37,"2021": 38,"2022": 40,"label": "Solvabiliteit (%)","2023*": 40,"2024**": 39},{"2020": 48,"2021": 49,"2022": 42,"label": "Netto schuldquote, gecorrigeerd voor doorgeleende gelden (%)","2023*": 41,"2024**": 41},{"2020": 7,"2021": 3,"2022": 5,"label": "Exploitatieruimte (%)","2023*": 2,"2024**": 2},{"2020": "69 (20%)","2021": "30 (9%)","2022": "5 (1%)","label": "Aantal gemeenten met 3 jaar op rij negatief resultaat","2023*": "9 (3%)","2024**": "10 (3%)"},{"2020": 7,"2021": 6,"2022": 5,"label": "Grondexploitatie (afgezet tegen totale baten)","2023*": 5,"2024**": 5},{"2020": -931,"2021": 426,"2022": 2184,"label": "EMU saldo (€ mln.)","2023*": -1223,"2024**": -2942},{"2020": 4.9,"2021": 4.7,"2022": 3.6,"label": "Stijging OZB-tarief, gecorrigeerd voor waardeontwikkeling (%)","2023*": 6,"2024**": 5.9},{"2020": 1,"2021": 1,"2022": 0.8,"label": "Netto investeringsquote (%)","2023*": 1.4,"2024**": 1.9}],"columnTypes": ["text","number","number","number","number","number"],"numberFormat": "nl","rowCount": 11,"columnCount": 6,"sectionRows": [],"bbox": [65.78,90.41,545.54,310.51],"pageSize": {"width": 595.25,"height": 842},"method": "camelot-stream","confidence": 0.89,"lowConfidence": false,"checks": [{"kind": "column-count","ok": true,"detail": "every row has 6 cells"},{"kind": "numeric-consistency","ok": true,"detail": "5 numeric column(s), all cells parse"},{"kind": "header","ok": true,"detail": "header: label | 2020 | 2021 | 2022 | 2023* | 2024**"}],"warnings": ["Column \"2020\" is numeric but 1 cell(s) are not numbers.","Column \"2021\" is numeric but 1 cell(s) are not numbers.","Column \"2022\" is numeric but 1 cell(s) are not numbers.","Column \"2023*\" is numeric but 1 cell(s) are not numbers.","Column \"2024**\" is numeric but 1 cell(s) are not numbers."],"merged": false,"textLayer": true,"cells": null,"llm": null,"part": null}}
Pricing
| Event | Name | What it means | Price |
|---|---|---|---|
apify-actor-start | Actor start | Charged by Apify when a run starts: once for a run of up to 1 GB of memory, and once more for every extra GB. It covers the first five seconds of compute. | $0.00005 per run up to 1 GB of memory ($0.05 per 1,000) |
page-processed | Page processed | One PDF page read and searched for tables in the deterministic pass. Pages skipped by the page range or the per-document cap are free; a document that cannot be downloaded or opened costs nothing. | $0.002 per page ($2.00 per 1,000) |
table-extracted (primary) | Table extracted | One table delivered as rows, records and metadata (page, position, headers, column types, confidence, checks). A table that continues over several pages counts once. | $0.01 per table ($10.00 per 1,000) |
llm-page-processed | LLM page (Claude Haiku 4.5) | One page read by Claude Haiku 4.5 from its image, for scanned pages or tables the deterministic pass could not read with confidence. Covers the API cost; tables found this way are charged as tables too. | $0.03 per page ($30.00 per 1,000) |
llm-page-processed-premium | LLM page (Claude Sonnet 5) | One page read by Claude Sonnet 5, for dense or hard layouts where Haiku is not enough. Covers the API cost; tables found this way are charged as tables too. | $0.06 per page ($60.00 per 1,000) |
These are list prices, which the Free plan pays. Paid Apify plans pay less for every event except the Actor start: 5% on Apify's Bronze tier, 10% on Silver and 15% on Gold and higher.
A 60-page report with 20 tables in fast mode costs $0.32, plus $0.00005 for the Actor start ($0.32005). The same report in llm mode with Haiku costs $2.12 (60 LLM pages), or $2.12005 with the start; with llm-fallback you pay the LLM price only for the pages that needed it. Documents that cannot be fetched, opened or held in the run's memory are not charged, except LLM pages the model had already read, and neither are pages skipped by the page range or the per-document cap. The deterministic pass on a page that the LLM later re-reads is not charged twice: the page price is charged once per page.
Worst case with the default limits: a run reads at most 200 pages per document (Maximum pages per document), so one document costs at most 200 x $0.002 = $0.40 in page charges, plus $0.01 for each table it delivers. The number of tables has no fixed cap; it is bounded by the table JSON the run can hold (32 MB per GB of memory, see Supported input and limits). The run start adds $0.00005 at the default 1 GB of memory. A run with the most documents the input accepts (500 URLs and 100 files, all different) can reach 600 x $0.40 = $240.00 in page charges before any table is counted. In llm-fallback and llm mode, Maximum LLM pages per run (default 200) adds at most 200 x $0.03 = $6.00 with Haiku or 200 x $0.06 = $12.00 with Sonnet. You can set a maximum cost per run in the run options in Apify Console. Once the pages read reach it, the Actor reads no further pages and does not try the remaining documents. Tables are charged as they are delivered, and delivery stops at the limit. The last tables and document records of the run can then be missing from the dataset, including the records of the documents not tried. Anything not delivered is not charged, except LLM pages the model already read.
An LLM page is charged as soon as the model has answered, because that is when its API cost is incurred: it stays charged if the run is aborted or fails before the tables are delivered. A model call that fails is not charged. Page and table events are charged when the results are delivered. If the platform restarts a run while its pages are being read (a migration), or you resurrect an aborted run, pages the model read before the restart are read again but not charged again, and Maximum LLM pages per run counts the pages of every attempt together.
FAQ
Why is a table split in two, or two tables joined?
Joining follows the column count and the header: a continuation with the same columns and the same header (or no header, starting at the top of the next page) is joined. Switch Join tables that continue on the next page off to get every page's table separately.
The header is label, column_2, column_3. Why?
No header row was recognised (the first row already held amounts). The header check says so and the confidence is lowered; give Expected columns to name them.
Can I get CSV or Excel instead of JSON?
The dataset can be exported as CSV or Excel from the Apify Console or API; records flattens well. For one CSV per table, render headers and rows.
Why does the LLM mode cost more?
Every page the model reads is an API call to Anthropic. The Haiku 4.5 page in the Actor's test recordings used 1,472 input and 812 output tokens, $0.0055 at the list prices the Actor uses, and one page's answer is capped at 4,096 output tokens. The LLM events in the pricing table cover that cost; the deterministic mode has no such cost and is therefore much cheaper. The log states per page what the call cost.
Is PDF table extraction with this Actor GDPR-compliant, and what happens to my documents?
Documents are downloaded into the run's temporary storage and deleted as soon as each one is processed (page images for the LLM after each batch of pages); nothing is kept between runs. In llm every page goes to the Anthropic API as a page image, under the Actor owner's account; in llm-fallback only the pages without a text layer and the pages whose tables all came out low confidence, so a page that has text but yielded no table is not sent; in fast nothing leaves the Apify platform. Results live in your own run's dataset and, while the run delivers them, in its own key-value store. If your documents contain personal data, you remain the controller under the GDPR; the example documents are public publications on open.overheid.nl, and the Actor is not affiliated with their publishers.
Related Actors
- Regulatory Page Change Monitor: know when a report or publication page changes, then extract the new tables.
- Crawlability & AI Crawler Access Auditor: whether a source allows automated fetching and has reserved text and data mining rights.
Support
A PDF whose tables come out wrong? Open an issue on the Actor page with the URL and the page number; layouts that fail are how the detectors get better.