# PDF Table Extractor (`ely_source/pdf-table-extractor`) Actor

Extract every table from PDF invoices, purchase orders, price lists and bank statements into clean JSON with real column names. Reads ruled and whitespace-aligned tables, flags scanned files, and never fails a whole batch because one file was unreachable.

- **URL**: https://apify.com/ely\_source/pdf-table-extractor.md
- **Developed by:** [Alexandre Leclerc](https://apify.com/ely_source) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 page processeds

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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

Turn the tables trapped inside your PDFs into clean, structured JSON — one row per
table row, ready for a spreadsheet, a database, or an AI agent.

Invoices, purchase orders, delivery notes, price lists, bank statements and reports
all carry their most valuable data in tables. Copying that out by hand is slow, and
most extraction tools hand back either a wall of text or a grid of anonymous cells.
This Actor gives you named columns you can use immediately.

### What does PDF Table Extractor do?

Give it one or more links to PDF files. It reads every page, finds the tables, uses
the first row as column names, and returns one dataset item per table row. Export
the result as JSON, CSV, Excel or XML in one click, or pull it through the Apify API.

It handles both kinds of table found in real documents:

- **Ruled tables**, where the cells are drawn with visible lines.
- **Whitespace-aligned tables**, where columns are only held together by spacing —
  the layout produced by ERP exports and bank statements, which most extractors miss
  entirely. Columns are recovered from the horizontal positions that repeat across
  lines, so a heading like `Unit price` stays in a single cell instead of splitting
  in two.

### Why use this PDF table extractor?

**It refuses to hand you garbage.** Chart figures and heat-maps are drawn with real
ruled cells, so naive extractors report them as tables and fill your dataset with
rows like `column_1: "tI"`. This Actor only keeps a table when its first row can
genuinely name the columns. On a 15-page research paper, that cut the noise from 47
rows to 2.

**It never loses a column.** When a PDF repeats a header or leaves one blank — routine
in invoices — a naive conversion to JSON silently overwrites data. Blank headers become
`column_2`, repeats become `Ref_2`, and every column survives.

**It tells you when a PDF is a scan.** Image-only documents come back with a clear
`no_text_layer` status instead of an empty result you have to debug.

**One bad file never kills the batch.** An unreachable URL is reported as a row with a
`download_failed` status, so a 500-document run always finishes and you can see exactly
which files need attention.

### What data can it extract?

| Field | Description |
|---|---|
| `sourceUrl` | The PDF the row came from |
| `status` | `ok`, `no_text_layer`, `no_tables_found`, `charge_limit_reached`, `download_failed` or `parse_failed` |
| `pageNumber` | Page the table was found on |
| `tableIndex` | Which table on that page |
| `data` | The row itself, keyed by column name |

### How to extract tables from a PDF

1. Click **Try for free**.
2. Paste one or more direct links to PDF files into the **PDF URLs** field. Each link
   must point at the file itself, not at a viewer page.
3. Leave the field empty if you just want to see the output format — a sample purchase
   order ships with the Actor and runs without any input.
4. Click **Start** and open the **Dataset** tab when the run finishes.
5. Export as CSV or Excel, or copy the API endpoint to pull the data automatically.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `pdfUrls` | array | `[]` | Direct links to the PDFs to process. Empty runs the bundled sample. |
| `minTableRows` | integer | `2` | Ignore tables smaller than this. Raise it if a layout produces small false positives. |
| `maxPages` | integer | `200` | Stop after this many pages per document, so an unexpectedly large file cannot run up the bill. |

```json
{
  "pdfUrls": [
    "https://example.com/invoice-2026-04.pdf",
    "https://example.com/purchase-order-147.pdf"
  ],
  "minTableRows": 2
}
````

### Output

```json
{
  "sourceUrl": "https://example.com/purchase-order-147.pdf",
  "status": "ok",
  "pageNumber": 1,
  "tableIndex": 1,
  "data": {
    "Part number": "RS-4521-A",
    "Description": "Deep groove ball bearing 6204",
    "Qty": "120",
    "Unit price": "8.40",
    "Lead time": "5 days"
  }
}
```

### Pricing

You pay per page processed, so the bill matches the work and nothing else. Pages
inside files that could not be downloaded are never charged.

| Event | Price |
|---|---|
| Actor start | $0.01 |
| Page processed | $0.01 |

#### How much does it cost to extract tables from 1,000 PDF pages?

About **$10**. A single 20-page invoice costs roughly **$0.20**. Charging per page
rather than per extracted row keeps the cost predictable: you know how many pages
your documents have before you run them, but never how many rows they contain.

### Integrations and API

Every run is available through the Apify API, so you can schedule extractions, push
results into Google Sheets, Airtable, Zapier or Make, or call the Actor from your own
backend. The Actor is also exposed through the Apify MCP server, which lets an AI
assistant read a PDF's tables directly as part of a larger workflow. The input is
deliberately small — a list of URLs — so it composes cleanly inside an agent.

### Works well with

Got scanned documents? Run them through
[PDF OCR](https://apify.com/ely_source/pdf-ocr) first to get a text layer, then bring
the result back here to pull the tables out of it.

### FAQ

#### Does it work with scanned PDFs?

Not directly. A scan is an image with no text layer, so there is nothing to extract
until it has been through OCR. Rather than returning an unexplained empty result, the
Actor flags those documents with the `no_text_layer` status — and **does not charge you
for them** — so you know exactly which files to route through
[PDF OCR](https://apify.com/ely_source/pdf-ocr) first.

#### Can it read tables that have no borders?

Yes. Columns are detected from the horizontal positions that recur across lines, which
covers whitespace-aligned layouts up to 12 columns. Wider layouts are picked up when
the PDF rules them with visible lines.

#### What happens to tables that span several pages?

They come back as one table per page. Join them on `sourceUrl` and the column names to
rebuild the full table.

#### Why did a table come back empty or missing?

Three common causes: the PDF is a scan (`no_text_layer`), the layout has no detectable
table (`no_tables_found` — try lowering `minTableRows`), or the "table" is actually a
chart figure, which is skipped on purpose because its cells hold no usable column names.

#### Is there a file size limit?

Yes, 50 MB per file. Larger documents should be split before processing.

#### Is it legal to extract data from PDFs?

You are responsible for having the right to process the documents you submit. This
Actor only reads files you point it at; it does not crawl or bypass any access control.

### Support

Found a bug or need a format handled that is not covered yet? Open an issue on the
Actor's **Issues** tab and it will be looked at. Feature requests for specific document
layouts are welcome — real examples make the Actor better for everyone.

# Actor input Schema

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

Direct links to the PDF files to process, for example invoices, purchase orders, delivery notes, price lists or reports. Each link must point at the PDF file itself, not at a viewer page. Leave empty to run a bundled sample purchase order so you can see the output format.

## `minTableRows` (type: `integer`):

Tables with fewer rows than this are ignored. Raise it to 3 or more when a document's layout produces small false-positive tables such as headers or address blocks.

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

Stop after this many pages in each document. Protects you from an unexpectedly large file running up the bill. Raise it when you knowingly process long reports.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://example.com/invoice-2026-04.pdf"
  ],
  "minTableRows": 2,
  "maxPages": 200
}
```

# Actor output Schema

## `tables` (type: `string`):

One item per table row, keyed by the column names found in the PDF. Files that failed appear here too, carrying a status field explaining why.

# 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": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("ely_source/pdf-table-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": [] }

# Run the Actor and wait for it to finish
run = client.actor("ely_source/pdf-table-extractor").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": []
}' |
apify call ely_source/pdf-table-extractor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "PDF Table Extractor",
        "description": "Extract every table from PDF invoices, purchase orders, price lists and bank statements into clean JSON with real column names. Reads ruled and whitespace-aligned tables, flags scanned files, and never fails a whole batch because one file was unreachable.",
        "version": "0.1",
        "x-build-id": "FtEwXyMvKLrpBScxF"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/ely_source~pdf-table-extractor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-ely_source-pdf-table-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/ely_source~pdf-table-extractor/runs": {
            "post": {
                "operationId": "runs-sync-ely_source-pdf-table-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/ely_source~pdf-table-extractor/run-sync": {
            "post": {
                "operationId": "run-sync-ely_source-pdf-table-extractor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "pdfUrls": {
                        "title": "PDF URLs",
                        "type": "array",
                        "description": "Direct links to the PDF files to process, for example invoices, purchase orders, delivery notes, price lists or reports. Each link must point at the PDF file itself, not at a viewer page. Leave empty to run a bundled sample purchase order so you can see the output format.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "minTableRows": {
                        "title": "Minimum rows per table",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Tables with fewer rows than this are ignored. Raise it to 3 or more when a document's layout produces small false-positive tables such as headers or address blocks.",
                        "default": 2
                    },
                    "maxPages": {
                        "title": "Maximum pages per file",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Stop after this many pages in each document. Protects you from an unexpectedly large file running up the bill. Raise it when you knowingly process long reports.",
                        "default": 200
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
