# PDF to Structured Data (Excel/JSON) with OCR (`nibble/pdf-to-structured-data`) Actor

Convert PDF invoices, forms, statements and reports into clean structured JSON (text, tables, key/values) with an OCR fallback for scanned pages.

- **URL**: https://apify.com/nibble/pdf-to-structured-data.md
- **Developed by:** [Simon Fletcher](https://apify.com/nibble) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 file converteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Structured Data turns **PDF invoices, receipts, purchase orders, bank statements, forms and reports into clean, structured JSON** — full text, detected tables, and key/value fields — with an **OCR fallback for scanned pages**. Send a URL, an uploaded file, or a base64 payload; get back agent-ready data you can push straight into Excel, a database, or an AI workflow. Built on the [Apify platform](https://apify.com/actors), so you get an API, scheduling, and [integrations](https://apify.com/integrations) (Make, Zapier, n8n) out of the box.

### What does PDF to Structured Data do?

It reads each PDF you give it and emits **one structured record per document**:

- **`text`** — the full document text, whitespace-normalized.
- **`tables`** — every detected table as a clean rows/cells matrix (no HTML).
- **`keyValues`** — `Label: value` fields parsed out automatically (invoice number, dates, totals, account numbers…).
- **`pages`** — an optional per-page breakdown of text and tables.

Output is **agent-first**: compact JSON with no HTML blobs or nested junk, so it drops directly into AI agents (via the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp)), spreadsheets, or a data pipeline.

### Why use PDF to Structured Data?

- **Back-office automation** — convert incoming invoices, receipts and POs into rows for accounting/ERP import.
- **Data extraction at scale** — batch hundreds of PDFs per run via the API or a schedule.
- **AI/RAG ingestion** — feed clean text + tables to an LLM without a bespoke parser.
- **No subscription lock-in** — pay only for what you convert (see Pricing), instead of a fixed monthly SaaS seat.

### How to use PDF to Structured Data

1. Open the **Input** tab.
2. Provide your PDFs one of three ways:
   - **PDF URLs** — paste public links to the files.
   - **Uploaded files** — upload PDFs (passed as key-value-store keys).
   - **Base64 PDFs** — inline payloads for API/agent callers.
3. (Optional) toggle table extraction, key/value detection, per-page breakdown, or the OCR fallback.
4. Click **Start**. Each converted document appears as a row in the **Output** dataset.

### Input

| Field | Type | Description |
| --- | --- | --- |
| `pdfUrls` | array | Public URLs of PDFs to download and convert. |
| `keyValueStoreKeys` | array | Keys in the run's key-value store holding uploaded PDF bytes. |
| `pdfBase64` | array | Base64-encoded PDF payloads (inline). |
| `includePages` | boolean | Include the per-page `pages` breakdown (default `true`). |
| `extractTables` | boolean | Detect and emit tables (default `true`). |
| `detectKeyValues` | boolean | Parse `Label: value` fields (default `true`). |
| `ocrFallback` | boolean | OCR scanned/image-only pages with Tesseract (default `true`). |

Example input:

```json
{
  "pdfUrls": [{ "url": "https://example.com/invoice-1001.pdf" }],
  "extractTables": true,
  "detectKeyValues": true
}
```

### Output

Each input PDF becomes one dataset item. You can download the dataset in **JSON, CSV, Excel, or HTML**.

```json
{
  "source": "invoice-1001.pdf",
  "status": "ok",
  "error": null,
  "pageCount": 1,
  "text": "INVOICE\nInvoice Number: INV-1001\nDate: 2026-03-14\nBill To: Acme Corp\nItem Qty Price\nWidget 2 10.00\nGadget 1 25.00\nTotal: 45.00",
  "pages": [
    {
      "page": 1,
      "text": "INVOICE\nInvoice Number: INV-1001\n...",
      "tables": [[["Item", "Qty", "Price"], ["Widget", "2", "10.00"], ["Gadget", "1", "25.00"]]]
    }
  ],
  "tables": [
    { "page": 1, "index": 0, "rows": [["Item", "Qty", "Price"], ["Widget", "2", "10.00"], ["Gadget", "1", "25.00"]] }
  ],
  "keyValues": {
    "Invoice Number": "INV-1001",
    "Date": "2026-03-14",
    "Bill To": "Acme Corp",
    "Total": "45.00"
  },
  "meta": { "extractor": "pdfplumber", "ocrUsed": false, "charCount": 126, "tableCount": 1 }
}
```

#### Output fields

| Field | Type | Description |
| --- | --- | --- |
| `source` | string | The URL, key, or label of the input PDF. |
| `status` | string | `ok` or `error`. |
| `error` | string / null | Failure reason when `status` is `error`. |
| `pageCount` | number | Number of pages in the PDF. |
| `text` | string | Full normalized document text. |
| `pages` | array | Per-page `{ page, text, tables }` (when `includePages`). |
| `tables` | array | Detected tables as `{ page, index, rows }`. |
| `keyValues` | object | Parsed `Label: value` fields. |
| `meta` | object | `extractor`, `ocrUsed`, `charCount`, `tableCount`. |

A document that fails to parse returns a row with `status: "error"` and a reason — one bad file never aborts the rest of the batch.

**Very large documents:** Apify caps a single dataset row at ~9 MB. When a converted PDF's `text` + `pages` + `tables` would exceed that, the full record is written to the run's **key-value store** and the dataset row is trimmed: `pages` and `tables` are omitted, `text` is truncated, and the row carries `truncated: true` plus `fullResultKey` (the key-value-store key of the complete, untruncated record). `keyValues` and `meta` always stay complete inline.

### Pricing

This Actor uses **pay-per-event**: you are billed **per successfully converted document**, so you only pay for results. High-volume users can be moved to a flat monthly rental tier — see the Actor's pricing details. On a per-document basis this is dramatically cheaper than fixed PDF-extraction SaaS subscriptions.

### Tips and advanced options

- **Batch for efficiency** — pass many PDFs in one run to amortize startup cost.
- **Turn off OCR** (`ocrFallback: false`) for digital (text-layer) PDFs to run faster.
- **Turn off `includePages`** if you only need document-level `text`, `tables` and `keyValues` — the output gets smaller.

### FAQ and support

- **Does it handle scanned PDFs?** Yes — pages with no text layer are OCR'd with Tesseract. Digital PDFs are extracted directly (faster, more accurate).
- **What about pages rotated sideways?** Handled automatically. Pages flagged as rotated 90/180/270 degrees are read via OCR on the upright-rendered image (Tesseract), so `keyValues` and text are recovered correctly instead of coming out with broken line order. The output sets `"ocrUsed": true` for these pages. Correctly-oriented digital pages are extracted directly and are unaffected.
- **What about personal data?** You supply your own documents; process only files you're authorized to. Don't submit documents containing data you may not process.
- **Found a problem?** Use the **Issues** tab on the Actor page — include a sample PDF and the output you expected.

# Actor input Schema

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

Public URLs of PDF files to convert. Each item is downloaded (with retries) and converted to structured data.

## `keyValueStoreKeys` (type: `array`):

Keys in this run's default key-value store that hold PDF bytes. This is how files uploaded via the Console form are passed to the Actor.

## `pdfBase64` (type: `array`):

Base64-encoded PDF payloads, for API or AI-agent callers that send the file inline instead of by URL.

## `includePages` (type: `boolean`):

Emit a 'pages' array with per-page text and tables. Turn off for a smaller, document-level-only output.

## `extractTables` (type: `boolean`):

Detect tables and emit them as row/cell matrices (great for invoices, statements, price lists).

## `detectKeyValues` (type: `boolean`):

Parse 'Label: value' lines (invoice number, dates, totals, …) into a keyValues object.

## `ocrFallback` (type: `boolean`):

If a page has no text layer (scanned/image-only), OCR it with Tesseract. Slower; leave on for mixed inputs.

## Actor input object example

```json
{
  "pdfUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ],
  "includePages": true,
  "extractTables": true,
  "detectKeyValues": true,
  "ocrFallback": true
}
```

# 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": [
        {
            "url": "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("nibble/pdf-to-structured-data").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": [{ "url": "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("nibble/pdf-to-structured-data").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ]
}' |
apify call nibble/pdf-to-structured-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nibble/pdf-to-structured-data",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/35Cw7SPU4rMGZQ9LW/builds/qdxk3R3CR0vRurZn7/openapi.json
