# PDF to JSON Extractor — Tables, Fields & Structure (`power_on/pdf-to-json-extractor`) Actor

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.

- **URL**: https://apify.com/power\_on/pdf-to-json-extractor.md
- **Developed by:** [Power On Labs](https://apify.com/power_on) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 page extractions

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PDF to JSON Extractor — Tables, Fields & Structure

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.

```json
{ "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` / `maxPages` are **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:

```json
{ "pdfUrls": ["https://example.com/invoice.pdf"] }
```

With options:

```json
{
  "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:

```js
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:

```python
from apify_client import ApifyClient

client = 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 `Authorization` header 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:

```json
{
  "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: true`
  and `needsOcr`, 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.

# Actor input Schema

## `pdfUrls` (type: `array`):

Direct links to the PDF files to extract. Accepts a plain URL, an Apify key-value store record URL (upload your file there first), or a base64 data URI (data:application/pdf;base64,...). From the API you can also pass objects: { "url": "...", "password": "...", "name": "invoice-42" }.

## `detectTables` (type: `boolean`):

Find tables and return them as rows and columns instead of a run of text. Works on borderless tables too, because detection is based on how the text lines up rather than on drawn rules.

## `extractKeyFields` (type: `boolean`):

Pull out invoice and order numbers, dates, totals, amounts with their currency, emails, phone numbers, IBANs and VAT IDs. IBANs are checked against the mod-97 checksum and ambiguous dates are flagged rather than guessed.

## `includeFormFields` (type: `boolean`):

Read AcroForm fields — the filled-in boxes of an interactive form — with their names, types and values.

## `includeLinks` (type: `boolean`):

Collect the hyperlink annotations on each page.

## `includeLines` (type: `boolean`):

Add every text line with its bounding box. Useful for building your own layout rules; it makes the output considerably larger.

## `outputFormats` (type: `array`):

Which files to write to the key-value store. JSON is the full structure; Markdown keeps headings and tables and is the format to feed an LLM; text is the plain reading-order text; CSV holds every detected table.

## `password` (type: `string`):

Password for encrypted PDFs, applied to every file that does not carry its own. Leave empty if the documents are not protected.

## `firstPage` (type: `integer`):

Page to start from, 1-based.

## `maxPages` (type: `integer`):

Stop after this many pages. 0 reads the whole document. Pages that are not read are not charged.

## `inlineFullResult` (type: `string`):

The complete per-page structure is always written to the JSON file. This decides whether it is also embedded in the dataset row. 'Auto' embeds it when the document is small enough for the 9 MB row limit.

## `concurrency` (type: `integer`):

How many PDFs to process at the same time. Raise it for many small files; lower it for very large ones.

## `retries` (type: `integer`):

Retries after a network failure. A wrong password, a missing file or a non-PDF response is final and is not retried.

## `timeoutSecs` (type: `integer`):

How long to wait for a single PDF to download before giving up.

## `maxFileSizeMb` (type: `integer`):

Files larger than this are refused with an explanatory row instead of being downloaded.

## `headers` (type: `object`):

Sent with every download. Use it for an Authorization header when the PDFs sit behind a private endpoint.

## `proxyConfiguration` (type: `object`):

Optional. Useful when the host serving the PDFs blocks datacenter addresses.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "detectTables": true,
  "extractKeyFields": true,
  "includeFormFields": true,
  "includeLinks": true,
  "includeLines": false,
  "outputFormats": [
    "json",
    "markdown"
  ],
  "firstPage": 1,
  "maxPages": 0,
  "inlineFullResult": "auto",
  "concurrency": 3,
  "retries": 2,
  "timeoutSecs": 120,
  "maxFileSizeMb": 100,
  "headers": {},
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pdfUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("power_on/pdf-to-json-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "pdfUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("power_on/pdf-to-json-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pdfUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call power_on/pdf-to-json-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,power_on/pdf-to-json-extractor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/22EkJ3Ap0M4R7iSGT/builds/Y2d0vTAt6zeuLNv3j/openapi.json
