# PDF Structured Table Extractor (`automation-lab/pdf-structured-table-extractor`) Actor

Extract structured tables from public PDF URLs into JSON rows and coordinate-aware cells with page provenance, confidence scores, and parsing warnings.

- **URL**: https://apify.com/automation-lab/pdf-structured-table-extractor.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/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 Structured Table Extractor

Turn tables in public digital PDFs into structured JSON rows and coordinate-aware cells. This **PDF table extractor** downloads up to 50 documents per run, detects table-like regions from positioned text, and preserves the source URL, file name, page number, table number, bounding box, confidence score, and parsing warnings.

Use the Actor for recurring document pipelines, spreadsheet exports, comparisons, and data-quality review. It works without a browser, proxy, account, or external AI API.

### What does PDF Structured Table Extractor do?

1. Validates that each input is a public HTTP(S) URL.
2. Follows only validated public redirects.
3. Enforces download timeout and file-size limits.
4. Verifies the downloaded bytes are a PDF.
5. Reads digital text and its coordinates with PDF.js.
6. Groups aligned text into rows, cells, and table records.
7. Writes one dataset item per detected table.
8. Stores per-document failures and run totals in the `OUTPUT` key-value-store record.

The default dataset is ready for JSON, CSV, Excel, Google Sheets, Make, Zapier, or custom ETL consumers.

### Who is it for?

- **Data engineers** ingesting recurring public reports.
- **Analysts** converting PDF tables to rows for spreadsheets or notebooks.
- **Compliance teams** reviewing structured values with page-level evidence.
- **Researchers** collecting comparable tables from document archives.
- **Automation builders** scheduling extraction and comparing later runs.

Choose [PDF Text Extractor](https://apify.com/automation-lab/pdf-text-extractor) when you need full document text rather than structured table cells. Choose [HTML Table Extractor](https://apify.com/automation-lab/html-table-extractor) for tables embedded in web pages.

### Why use it?

Unlike plain PDF-to-text output, every result keeps a two-dimensional `rows` array and flattened `cells` with row/column indexes and PDF coordinates. Quality signals make ambiguous output reviewable instead of hiding uncertainty. One bad URL does not discard successful documents in the same batch.

No OCR or generative model is used, so digital PDFs are processed predictably and economically. Private-network destinations are rejected to protect your run from server-side request forgery.

### Input parameters

| Field | Type | Default | Purpose |
|---|---|---:|---|
| `urls` | string\[] | required | 1–50 direct public PDF URLs |
| `maxTables` | integer | 100 | Global cap on emitted table records |
| `pageNumbers` | integer\[] | all | Optional one-based pages to process in every PDF |
| `includeHeaders` | boolean | `true` | Separate a first row only when it looks distinct from later data rows |
| `minRows` | integer | 3 | Minimum aligned rows for a detected table |
| `minColumns` | integer | 2 | Minimum positioned cells in each qualifying row |
| `maxFileSizeMb` | integer | 25 | Reject larger documents, maximum 100 MB |
| `timeoutSecs` | integer | 60 | Per-download timeout, from 5 to 300 seconds |

Raising `minRows` can suppress short list-like regions. Raising `minColumns` focuses extraction on wider tables. Use `pageNumbers` when a large report has known table pages.

### Getting started

1. Open the Actor input page.
2. Add one or more direct public PDF URLs.
3. Keep the default detection settings for the first run.
4. Click **Start**.
5. Open **Dataset → Detected tables**.
6. Review `confidence` and `parsingWarnings` before downstream use.
7. Export the dataset or connect it to your workflow.

Try this real input:

```json
{
  "urls": [
    "https://raw.githubusercontent.com/tabulapdf/tabula-java/master/src/test/resources/technology/tabula/campaign_donors.pdf"
  ],
  "maxTables": 25,
  "minRows": 3,
  "minColumns": 2,
  "includeHeaders": true
}
```

### Output fields

| Field | Meaning |
|---|---|
| `sourceUrl` | Final validated PDF URL after redirects |
| `fileName` | File name from response headers or URL |
| `pageNumber` | One-based source page |
| `tableIndex` | Table sequence within the source document |
| `headers` | Inferred header row, or `null` |
| `rows` | Two-dimensional array of structured data rows |
| `cells` | Flattened cells with row/column indexes and coordinates |
| `rowCount`, `columnCount` | Detected table dimensions |
| `boundingBox` | Combined PDF-coordinate bounds of the table |
| `extractionMethod` | `digital-text-position` in this release |
| `confidence` | Heuristic score from 0 to 1 |
| `qualitySignals` | Column consistency, fill ratio, and page text-item count |
| `parsingWarnings` | Review signals for inconsistent, empty, or ambiguous structure |
| `extractedAt` | ISO timestamp |

### Real output example

A local run on the campaign donor example detected a 55-row, 7-column table with 0.99 confidence:

```json
{
  "type": "table",
  "sourceUrl": "https://raw.githubusercontent.com/tabulapdf/tabula-java/master/src/test/resources/technology/tabula/campaign_donors.pdf",
  "fileName": "campaign_donors.pdf",
  "pageNumber": 1,
  "tableIndex": 1,
  "headers": ["Apellido", "Nombre", "Matricula", "Cuit", "Fecha", "Tipo", "Importe"],
  "rows": [["Abal Medina", "Juan Manuel", "...", "...", "...", "...", "..."]],
  "rowCount": 55,
  "columnCount": 7,
  "confidence": 0.99,
  "qualitySignals": {
    "consistentColumnRatio": 0.98,
    "filledCellRatio": 1,
    "detectedTextItems": 405
  },
  "parsingWarnings": []
}
```

The actual `cells` array also includes `rowIndex`, `columnIndex`, `value`, `x`, `y`, `width`, and `height` for every detected cell.

### Quality and parsing-error signals

`confidence` is not a claim that every semantic column is perfect. It measures geometric consistency and filled cells. Review records when:

- `parsingWarnings` is non-empty;
- merged or spanning cells create inconsistent column counts;
- a document uses multi-line headers;
- a numbered list is geometrically similar to a two-column table;
- layout changes between scheduled runs.

Download failures are listed in the `OUTPUT` record with their URL and error. If every document fails, the run fails with a non-zero status. If a valid digital PDF has no qualifying table, it completes with zero table records.

### How much does it cost to extract structured PDF tables?

Pricing uses one `start` event per run and one `table` event per detected table. The current source configuration starts at **$0.005 per run** and the BRONZE table rate is **$0.0196 per table**, with lower per-table rates at higher usage tiers.

At the BRONZE rate:

- 5 detected tables: about **$0.103** total.
- 25 detected tables: about **$0.495** total.
- 100 detected tables: about **$1.965** total.

These examples combine the start fee and table events. The platform shows the active tier and maximum charge before each run. Failed downloads and PDFs with no detected table do not produce table events.

### Batch and recurring workflows

- Schedule a monthly batch of public financial reports.
- Compare normalized `rows` with the previous dataset.
- Alert when table dimensions or selected values change.
- Export new table records to Google Sheets or a warehouse.
- Keep `sourceUrl`, `pageNumber`, and cell coordinates with every transformed record for auditability.

For recurring documents, use stable direct URLs where possible and retain the source dataset ID alongside your transformed output.

### Export to CSV or Excel

Each dataset item represents one table and contains nested rows. JSON preserves the complete structure. For flat CSV or Excel output, map each element of `rows` to a separate downstream row and copy `sourceUrl`, `pageNumber`, and `tableIndex` onto it. Apify integrations, Make, Zapier, or a short script can perform that expansion.

### Run with the Apify API

Replace `APIFY_TOKEN` with your token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~pdf-structured-table-extractor/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf"],"maxTables":10}'
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/pdf-structured-table-extractor').call({
  urls: ['https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf'],
  maxTables: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
from apify_client import ApifyClient
import os

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/pdf-structured-table-extractor').call(run_input={
    'urls': ['https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf'],
    'maxTables': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/pdf-structured-table-extractor"
```

#### Claude Desktop

Add this JSON under `mcpServers` in the Claude Desktop configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/pdf-structured-table-extractor"
    }
  }
}
```

#### Cursor

Add the same `apify` server URL in **Cursor Settings → MCP**.

#### VS Code

Add the same `apify` server URL to your VS Code MCP server configuration.

Example prompts:

- “Extract tables from these three public report URLs and summarize warnings.”
- “Run the PDF table extractor on pages 4, 7, and 9, then return source coordinates for cells containing percentages.”
- “Compare structured rows in this month’s PDF with the previous dataset.”

### Limits

- Digital-text PDFs only; image-only scans need OCR before this Actor.
- Password-protected or encrypted PDFs may fail.
- It does not log in, solve CAPTCHAs, or access private files.
- Irregular, rotated, borderless, spanning, or deeply nested tables may need threshold adjustment or manual review.
- A geometric detector can classify aligned numbered lists as tables.
- `pageNumbers` applies to every URL in a batch.
- The Actor does not edit PDFs and is not a PDF table editor.

### Legality and responsible use

Process only documents you are authorized to download and reuse. Respect copyright, licensing terms, privacy obligations, and source-site policies. Public availability does not automatically permit every redistribution or commercial use. Avoid exposing personal or sensitive data through public datasets.

### Troubleshooting

**The run returned zero tables.** Confirm the PDF contains selectable text, remove `pageNumbers`, lower `minRows` to 2, or lower `minColumns` to 2. An image-only scan requires OCR.

**Columns are split or merged incorrectly.** Review `cells` and coordinates, adjust detection thresholds, and keep `parsingWarnings` with downstream records.

**The URL works in my browser but fails here.** Use a direct public PDF URL without login, cookies, expiring access, or a private-network address. Check the `OUTPUT` record for the exact HTTP, size, timeout, or signature error.

**One URL failed in a batch.** Successful documents still produce results. Failed URL details remain in `OUTPUT`; rerun only those URLs after fixing access.

### FAQ

#### Does it use OCR or AI?

No. This release uses PDF.js digital text positions. That keeps extraction deterministic and inexpensive, but scanned pages are unsupported.

#### Can it process several PDFs?

Yes. Supply up to 50 URLs and cap total output with `maxTables`.

#### Are headers always removed from rows?

No. With `includeHeaders: true`, the first row is separated only when it differs enough from later numeric data rows. Otherwise `headers` is `null` and all detected rows remain in `rows` with a warning.

#### Can I trace a value back to the PDF?

Yes. Every table includes the source URL and page number; every flattened cell includes PDF coordinates.

#### Does it charge for failed URLs?

Failed URLs do not produce `table` events. A run still has its one-time `start` event.

### Related Automation Lab Actors

- [PDF Text Extractor](https://apify.com/automation-lab/pdf-text-extractor) — extract full and per-page PDF text and metadata.
- [HTML Table Extractor](https://apify.com/automation-lab/html-table-extractor) — extract structured tables from web pages.
- [JSON Diff Tool](https://apify.com/automation-lab/json-diff-tool) — compare normalized table output between recurring runs.

### Support

If a reproducible digital PDF is parsed incorrectly, open an Actor issue with the public URL, input, expected page/table, and a link to the run. Do not include private documents or credentials.

# Actor input Schema

## `urls` (type: `array`):

One to 50 direct public HTTP(S) PDF URLs. Private-network URLs and files requiring login are rejected.

## `maxTables` (type: `integer`):

Stop after this many table records across all documents.

## `pageNumbers` (type: `array`):

Optional one-based page numbers to process in every PDF. Leave empty to process all pages.

## `includeHeaders` (type: `boolean`):

Return the first detected table row separately in the headers field.

## `minRows` (type: `integer`):

Minimum aligned rows required before a region is returned as a table.

## `minColumns` (type: `integer`):

Minimum positioned cells per row required for table detection.

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

Reject a document larger than this limit before parsing.

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

Per-request timeout used while downloading each PDF.

## Actor input object example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf"
  ],
  "maxTables": 100,
  "includeHeaders": true,
  "minRows": 3,
  "minColumns": 2,
  "maxFileSizeMb": 25,
  "timeoutSecs": 60
}
```

# Actor output Schema

## `overview` (type: `string`):

Open structured table records with source and page provenance, rows, cells, confidence, and parsing warnings.

# 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 = {
    "urls": [
        "https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/pdf-structured-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 = { "urls": ["https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/pdf-structured-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 '{
  "urls": [
    "https://raw.githubusercontent.com/camelot-dev/camelot/master/docs/_static/pdf/foo.pdf"
  ]
}' |
apify call automation-lab/pdf-structured-table-extractor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/MajfxgB8pLdiqZZmI/builds/V2CuwzS4wLxuy1IKu/openapi.json
