PDF to JSON Extractor — Tables, Fields & Structure
Pricing
from $1.40 / 1,000 page extractions
PDF to JSON Extractor — Tables, Fields & Structure
PDF to JSON API: tables as real rows and columns, headings, paragraphs in true reading order, metadata, form fields and key fields (invoice number, dates, totals, IBAN, VAT). Also outputs Markdown for RAG. Pay per page extracted, capped per document.
Pricing
from $1.40 / 1,000 page extractions
Rating
0.0
(0)
Developer
Power On Labs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Turn a PDF into structured JSON: tables as real rows and columns, headings and paragraphs in true reading order, document metadata, form fields, and the key values you actually want — invoice number, dates, totals, IBAN, VAT ID.
Most PDF extractors hand back a wall of text. That is fine if a human is going to read it. It is useless if a script has to find the total of an invoice, or if a retrieval system has to keep a financial table intact. This Actor keeps the structure.
{ "pdfUrls": ["https://example.com/invoice.pdf"] }
What comes back
| Tables as rows and columns | Every table becomes a 2-D array with a detected header row, plus the page and the bounding box it came from. Borderless tables are found too: detection reads how the text lines up, not the drawn rules — so the tables that line-based tools miss come through. |
| True reading order | A two-column paper, a newsletter, a report with a sidebar: the text comes out in the order a person reads it, not in the order the file happens to draw it. Recursive whitespace segmentation separates columns from full-width headings before anything is read. |
| Headings and paragraphs | Blocks are typed — heading (with a level), paragraph, listItem — using the document's own type scale, so an 8 pt body with 11 pt titles is read as well as a 11 pt body with 24 pt titles. |
| Key fields, checked | Invoice and order numbers, dates, totals and amounts with their currency, emails, phones, IBANs, VAT IDs. IBANs are validated with the mod-97 checksum and Italian VAT numbers with their check digit, so an invalid one is dropped instead of returned. |
| Dates resolved, or flagged | 03/04/2026 is 3 April in Europe and 4 March in the US. If the document elsewhere shows its convention, the date is resolved and marked assumedOrder. If it does not, iso stays null and both readings are returned. A wrong due date is worse than a missing one. |
| Form fields | AcroForm fields with names, types and filled-in values — for W-9s, tax forms, applications, contracts. |
| Metadata and bookmarks | Title, author, subject, creator, producer, creation and modification dates as ISO timestamps, page count, PDF version, encryption and signature flags, plus the bookmark tree. |
| Markdown for RAG | The same structure rendered as Markdown, headings and pipe tables included. A table pasted as flat text is noise inside a retrieval system; the same table in Markdown stays queryable. |
| Scanned PDFs are labelled, not faked | A page with no text layer is reported as such (isScanned, needsOcr) and is not charged. See the limits below. |
Pricing — you pay for pages, and only the ones that worked
$0.002 per page extracted. Nothing else. No start fee, no per-document fee.
- A page that has no text layer (a scan) is not charged.
- A file that fails — dead link, wrong password, not a PDF — is not charged.
- Pages skipped with
firstPage/maxPagesare not charged. - At most 40 pages are charged per document. A 500-page manual costs the same as a 40-page one: $0.08. Long documents stay predictable.
A five-page invoice costs one cent.
Quick start
Minimal input:
{ "pdfUrls": ["https://example.com/invoice.pdf"] }
With options:
{"pdfUrls": [{ "url": "https://example.com/statement.pdf", "password": "hunter2", "name": "march-statement" },"https://example.com/report.pdf"],"detectTables": true,"extractKeyFields": true,"outputFormats": ["json", "markdown", "csv"],"maxPages": 20}
From the JavaScript client:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<APIFY_TOKEN>' });const run = await client.actor('power_on/pdf-to-json-extractor').call({pdfUrls: ['https://example.com/invoice.pdf'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items[0].fields.totals); // [{ label: 'total due', value: 1220, currency: 'EUR' }]console.log(items[0].tables[0].rows); // [['Item', 'Qty', 'Price'], ['Widget', '2', '9.90']]
From Python:
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("power_on/pdf-to-json-extractor").call(run_input={"pdfUrls": ["https://example.com/invoice.pdf"]})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["fields"], item["tables"])
It also works with no code at all through Apify's Zapier, Make and n8n integrations, or straight from the REST API.
Where the PDFs can come from
- A public URL — the ordinary case.
- A private endpoint — put an
Authorizationheader in Extra HTTP headers. - A file you have on disk — upload it to an Apify key-value store and pass the record
URL, or inline it as a base64 data URI:
data:application/pdf;base64,JVBERi0xLjQK….
What comes out
One dataset row per input file, plus the files themselves in the key-value store. Row shape:
{"url": "https://example.com/invoice.pdf","ok": true,"pageCount": 3,"pagesParsed": 3,"pagesWithText": 3,"pagesCharged": 3,"isScanned": false,"needsOcr": false,"tableCount": 2,"metadata": { "title": "Invoice 42", "author": "Acme", "createdAt": "2026-03-14T09:12:00.000Z", "pageCount": 3, "encrypted": false, "hasAcroForm": false },"fields": {"identifiers": { "invoiceNumber": "INV-2026-0042", "orderNumber": "PO-99812" },"totals": [{ "label": "total due", "value": 1220, "currency": "EUR", "raw": "EUR 1.220,00" }],"dates": [{ "raw": "03/04/2026", "iso": "2026-04-03", "assumedOrder": "day-first", "readings": ["2026-04-03", "2026-03-04"] }],"ibans": ["IT60X0542811101000000123456"],"emails": ["billing@acme.example"]},"tables": [{"page": 2,"rowCount": 4,"columnCount": 3,"header": ["Item", "Qty", "Price"],"rows": [["Item", "Qty", "Price"], ["Widget", "2", "9.90"]],"bbox": { "x0": 72, "y0": 310, "x1": 523, "y1": 402 }}],"pages": [{"page": 1,"width": 595.3,"height": 841.9,"rotation": 0,"hasTextLayer": true,"text": "…","blocks": [{ "type": "heading", "level": 1, "text": "Invoice", "bbox": { "x0": 72, "y0": 60, "x1": 240, "y1": 84 } }],"tables": []}],"jsonUrl": "https://api.apify.com/v2/key-value-stores/…/records/001-invoice.json?signature=…","markdownUrl": "…"}
rows always contains every row of the table, header included; header is a convenience
copy of the header row when one was detected.
The file links are signed, so they open in a browser or from curl without a token.
When a file goes wrong
One broken PDF never takes down the run. Every input gets a row, and a failed one carries
ok: false with an error and an errorCode:
errorCode | What happened |
|---|---|
PASSWORD_REQUIRED | The PDF is encrypted and no password was given |
PASSWORD_WRONG | The password given does not open it |
INVALID_PDF | Truncated, corrupt, or the URL returned an HTML page instead of a file |
EXTRACTION_FAILED | Network failure, timeout, size limit, HTTP error |
A page that fails on its own is recorded in pageErrors and the rest of the document is
still extracted.
Known limits — read this before you buy
- No OCR. A scanned PDF has no text layer, and this Actor does not run optical
character recognition on it. It detects the situation, returns
isScanned: trueandneedsOcr, and does not charge you for those pages. If your documents are scans, you need an OCR tool, not this one. - Formulas and code listings can occasionally be reported as small tables.
- Tables split across a page break come back as two tables, one per page.
- Right-to-left scripts are extracted but the reading order within a line follows the file's own text order.
These are stated on purpose. It is cheaper for both of us if you find out here rather than after a run.
Everything you can set
| Option | Default | What it does |
|---|---|---|
pdfUrls | — | URLs, {url, password, name} objects, or base64 data URIs |
detectTables | true | Find tables and return them as rows and columns |
extractKeyFields | true | Invoice numbers, dates, totals, IBAN, VAT, emails, phones |
includeFormFields | true | AcroForm fields with their values |
includeLinks | true | Hyperlink annotations per page |
includeLines | false | Every text line with its bounding box |
outputFormats | ["json","markdown"] | Also text and csv |
password | "" | Applied to files that carry no password of their own |
firstPage / maxPages | 1 / 0 | Read a slice of the document; skipped pages are free |
inlineFullResult | "auto" | Embed the full structure in the dataset row, or link to it |
concurrency | 3 | Documents in parallel |
retries | 2 | Retries on network failures only |
timeoutSecs | 120 | Download timeout |
maxFileSizeMb | 100 | Larger files are refused with an explanation |
headers | {} | Extra HTTP headers for private endpoints |
proxyConfiguration | off | For hosts that block datacenter addresses |
Good uses
Invoice and receipt automation · feeding contracts and reports into a RAG index · pulling financial tables out of annual reports and statements · reading filled-in tax and application forms · turning research papers into clean Markdown · migrating a document archive into a database.
Built by Power On Labs. Something extracted wrong? Open an issue on the Actor with the PDF or a link to it and it gets fixed — the extraction rules are ours, not a third-party library's default settings.