PDF to JSON API: text, tables and metadata from a PDF URL
Coming soon to the StoreTurn any public PDF link (annual report, invoice, government notice, price list) into clean JSON: text per page in reading order (markdown headings and tables), tables as rows with a header guess, and title, author and dates. Built for AI agents that meet a PDF mid-task and for anyone who needs PDF data in a pipeline. Pricing: $0.50 per 1,000 pages ($0.0005 per page with text), no start fee. Failed files and scanned pages without text are free.
Pricing: $0.50 per 1,000 pages ($0.0005 per page with text), no start fee. Failed files and scanned pages without text are free.
Questions agents ask
- “Get the wage bracket table on page 14 of https://www.irs.gov/pub/irs-pdf/p15t.pdf as rows.”
Example
Input
{ "urls": ["https://www.sec.gov/files/form10-k.pdf"], "pages": "1-5,10", "extract": ["text", "tables"] }Output
{
"schema_version": "1",
"input_index": 0,
"url": "https://www.sec.gov/files/form10-k.pdf",
"final_url": "https://www.sec.gov/files/form10-k.pdf",
"file_size_bytes": 549322,
"page_count": 19,
"pages_processed": 2,
"pages_charged": 2,
"title": "Form 10-K",
"author": "U.S. Securities and Exchange Commission",
"subject": "Annual Report Pursuant to Section 13 or 15(d) of the Securities Exchange Act of 1934",
"creator": "Acrobat PDFMaker 24 for Word",
"producer": "Adobe PDF Library 24.1.163",
"creation_date": "2024-05-12T10:49:53-04:00",
"modification_date": "2026-09-10T12:50:48-04:00",
"pages": [
{
"page": 1,
"text": "UNITED STATES\nSECURITIES AND EXCHANGE COMMISSION\nWashington, D.C. 20549\n\nOMB APPROVAL\nOMB Number: 3235-0063\n..."
},
{
"page": 2,
"text": "(2) Particular attention is directed to Regulation 12B which contains general requirements\n..."
}
],
"tables": [],
"warnings": [],
"error": null,
"source_url": "https://www.sec.gov/files/form10-k.pdf",
"retrieved_at": "2026-11-05T22:41:04+00:00"
}What you get
| Field | Type | Meaning |
|---|---|---|
schema_version | string | Output schema version. |
input_index | integer | Position of this PDF in the input, 0-based: urls[input_index], or len(urls) for pdfBase64. Rows are written as each PDF finishes, so their order can differ from the input. |
url | string | null | The PDF URL as given in the input; null for pdfBase64. |
final_url | string | null | URL the PDF was finally downloaded from, after redirects; null for pdfBase64 or on error. |
file_size_bytes | integer | null | Size of the PDF file in bytes; null when it could not be downloaded. |
page_count | integer | null | Number of pages in the PDF; null on error. |
pages_processed | integer | Pages read in this run (after pages, maxPages and size limits). |
pages_charged | integer | Page events charged for this PDF: pages that had text (1 for a metadata-only request, 0 on error). |
title | string | null | Title from the PDF's document info; null if not set or metadata not requested. |
author | string | null | Author from the document info. |
subject | string | null | Subject from the document info. |
creator | string | null | Application that created the original document, e.g. Microsoft Word. |
producer | string | null | Application that wrote the PDF. |
creation_date | string | null | Creation date, ISO 8601 with the PDF's UTC offset when given, e.g. 2024-05-12T10:49:53-04:00. |
modification_date | string | null | Last modification date, ISO 8601. |
pages | array | null | One entry per processed page: {page (1-based), text}. text is markdown-ish (## headings, | tables |) or plain, in reading order; null for pages without a text layer. null when text was not requested. |
tables | array | null | Tables detected from aligned text: {page, header (first row when it looks like a header, else null), rows (arrays of cell strings, null = empty cell), n_cols}. null when tables were not requested. |
warnings | array | Things to know, e.g. scanned pages without text (OCR not supported, not charged) or pages left out by maxPages. |
error | string | null | Why this PDF failed (refused URL, download error, not a PDF, password-protected, unreadable, too complex: over the parse timeout or out of memory); null on success. Failed PDFs are not charged. |
source_url | string | null | Where the data came from (the input URL); null for pdfBase64. |
retrieved_at | string | UTC time the PDF was processed. |
Use with AI agents (MCP)
Add this URL to your MCP client (Claude, Cursor, VS Code, ...) to load the Actor as a tool through the Apify hosted MCP server. It signs you in with OAuth or accepts Authorization: Bearer <APIFY_TOKEN>. It works once the Actor is live on the Apify Store.
https://mcp.apify.com?tools=bosco_labs/pdf-to-data
For “Get the wage bracket table on page 14 of https://www.irs.gov/pub/irs-pdf/p15t.pdf as rows.” the agent passes:
{"urls": ["https://www.irs.gov/pub/irs-pdf/p15t.pdf"], "pages": "14", "extract": ["tables"]}
one JSON row per PDF with schema_version, source_url and a page number on every page and table, so the agent can cite where a value came from. Errors come back as a row with an error text, never as a crashed run.
Or call the Apify API: POST https://api.apify.com/v2/acts/bosco_labs~pdf-to-data/run-sync-get-dataset-items with the input as JSON body. Cost: $0.0005 per page with text.
Input fields
| Field | Type | Meaning |
|---|---|---|
urls | array | 1 to 20 public http(s) links to PDF files, e.g. ["https://www.sec.gov/files/form10-k.pdf"]. Each PDF gives one output row. Files up to 50 MB; links to private or local addresses are refused. Empty = use pdfBase64 instead. |
pdfBase64 | string | Instead of (or besides) URLs: the PDF file itself, base64-encoded, optionally as a data: URI, e.g. "JVBERi0xLjQK...". Up to 50 MB decoded (Apify limits the whole input to a few MB, so use URLs for big files). Empty = none. |
pages | string | Which pages to read, 1-based, as ranges and single pages separated by commas, e.g. "1-5,10". Applies to every PDF; pages past the end are skipped with a warning. Empty = all pages (up to maxPages). |
extract | array | Any of text, tables, metadata, e.g. ["tables"]. Default (empty) = all three. Metadata alone costs one page per PDF. |
maxPages | integer | Most pages read per PDF, counted from the first selected page, e.g. 10. Default 50. You pay per page with text, so this caps the cost per PDF; a warning tells you how to request the rest. |
outputFormat | string | How page text is written: "markdown" marks headings with ## and writes detected tables as markdown tables; "text" gives plain paragraphs and tab-separated table rows. Default markdown. The tables field is the same in both. |
timeoutSecs | integer | Most seconds spent parsing one PDF, e.g. 60. Default 120. Pages are read until 3/4 of this time (later pages get a warning and are not charged); a PDF still parsing at the limit is stopped and returns an error row, not charged. Empty = 120. |
Sources, licence and limits
Scanned PDFs? Pages that are only an image (scans, photos) have no text layer. They come back with text: null and a warning, and are not charged. OCR is not supported.
How good is table detection? Tables are found from aligned text: three or more lines in a row with two or more separate columns. Clean column layouts (price lists, statements, rate tables) work well. Limits: cells that wrap over several lines come out as extra rows; columns closer than about two characters merge into one cell; ruled tables are read from their text, not their lines; a table next to running text can pick up that text as a column. The page text always has everything, in reading order.
Which PDFs are refused? Links that are not http(s), hosts that resolve to private, local or cloud-metadata addresses (also after redirects), files over 50 MB, responses that are not a PDF (e.g. a login page), password-protected and broken files. Each gives a row with an error and costs nothing.
What does a metadata-only run cost? One page per PDF (extract: ["metadata"]).
Are my files stored? No. Each PDF is downloaded into memory, parsed and dropped. Only the extracted data goes to your dataset; nothing from the document is logged.
Size limits? 50 MB per file, up to 500 pages per PDF per run (maxPages, default 50), and about 8 MB of output per PDF. A warning tells you which pages to ask for next.
Very complex or hostile PDFs? Each PDF is parsed in its own process with a time limit (timeoutSecs, default 120 s) and a memory limit. Pages are read until 3/4 of that time, then the rest gets a warning. A PDF that is still stuck at the limit (for example one page that inflates to tens of MB of drawing commands) is stopped and returns an error row ("PDF too complex"), costs nothing, and the other PDFs in the run are delivered as usual.
Speed? Measured locally: 19 text pages in 2 s, 20 pages of the EU GDPR in 5 s, 50 pages of IRS Publication 15 in 10-20 s (parsing time, download excluded).