# PDF Column Recipe (`anchored_quarterstaff/pdf-column-recipe`) Actor

Extract repeatable tables from public text PDFs using reusable column boundaries. Preserve blank cells and page coordinates; export JSON and CSV. No OCR or model API.

- **URL**: https://apify.com/anchored\_quarterstaff/pdf-column-recipe.md
- **Developed by:** [PDF Recipe Lab](https://apify.com/anchored_quarterstaff) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 successful pdf documents

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/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 Column Recipe

Apply the same explicit column layout to every page of a public text PDF. Get a single JSON result, row dataset and spreadsheet-safe CSV, with blank cells kept in position and source coordinates for each cell.

Useful for recurring public catalogs, data dictionaries and reports with a stable layout, especially when automatic table detection guesses the wrong columns. You choose the layout once and reuse the JSON recipe in an Apify task. No OCR, paid model API, proxy subscription or custom document service.

### Try the included example

```json
{"useDemo": true}
```

The original synthetic catalog has two pages and four data rows. The second page uses a longer title, repeats the column header, and retains the same column positions. An intentionally blank quantity stays blank; `0` stays `0`. The demo has no developer event charge.

### Process a public PDF

```json
{
  "useDemo": false,
  "pdfUrl": "https://your-public-site.example/catalog.pdf",
  "recipe": {
    "pageWidth": 612,
    "pageHeight": 792,
    "top": 100,
    "bottom": 700,
    "boundaries": [40, 180, 330, 450, 570],
    "columns": ["Code", "Item", "Quantity", "Unit"],
    "skipExactHeader": true,
    "rowTolerance": 2
  }
}
```

The URL above is an illustrative placeholder, not a live test endpoint. A complete tested public-data input is included below.

Coordinates are PDF points (72 per inch), measured from the **top-left** of the page. Set the page width and height, choose a vertical region excluding titles and footnotes, then put boundaries in the spaces between columns. Four columns require five boundaries. Names must be unique. All pages must match the recipe's size within 0.5 point. Save the input as an Apify task if you need to reuse it; this Actor does not create schedules itself.

You need to know the page layout. This version has no visual template designer or automatic coordinate setup. If your document changes layout, review and change the recipe before using its results.

### Output

- `OUTPUT`: full result, recipe, SHA-256 of the input PDF, page reports and rows.
- Default dataset: one item per physical row, including `page`, `rowOnPage`, `values`, `fields`, `cellBoxes`, `issues` and `unassigned`.
- `TABLE.csv`: page, row number, named columns and issue codes. Formula-like values get an apostrophe prefix for spreadsheet import; JSON keeps the original text.
- `SUMMARY`: document and page diagnostics without row text or source URL.

An empty cell is an empty string with a null bounding box. Bounding boxes use `[left, top, right, bottom]`. A word crossing a column boundary or crop edge goes into `unassigned` with its coordinates and sets a warning, instead of being silently put into a neighboring cell. Check warnings before downstream use.

### Fixed limits and known limitations

- One PDF per run, HTTPS port 443 only, no URL credentials, query strings, fragments or private-network addresses. Redirect targets are revalidated; connections use a validated IP and verify TLS for the original host. No cookies or authentication headers.
- At most 10 MiB, 50 pages, 2,000 output rows, 60,000 characters or 10,000 words per page. A hard worker timeout is 55 seconds. Configure the cloud run for **512 MiB and 90 seconds**. Limits reject the whole document rather than returning a truncated dataset.
- Text PDFs only. Blank or image-only pages return `NO_EXTRACTABLE_TEXT`; this is not an OCR diagnosis. Rotated pages/text and mixed page sizes are rejected.
- Each output row is a physical text line. Wrapped records, merged cells and rows split across page breaks are **not automatically reassembled**. Multiple different layouts need separate runs/recipes.
- Exact headers matching all supplied column names can be skipped. Multi-line, differently spelled or reordered headers are retained. A data row identical to the column names will also be skipped: disable `skipExactHeader` for that case.
- The selected region deliberately omits everything outside it. A wrong recipe can exclude data or assign text to an unintended column without a warning. `ok` means no detected structural warning, **not independently verified semantic accuracy**.
- Fonts, ligatures and unusually positioned characters can alter extracted words. Row tolerance groups words by top coordinate, not by a document's semantic record boundaries.
- Use only non-confidential public reports/catalogs you may process. No personal, medical, financial-account, identity or employment records. The Actor is not a personal-data detection tool; it cannot identify every sensitive document automatically.
- Input and extracted results remain in Apify storage under your account's retention rules. This is not a zero-storage service. No external analytics or model calls are made by the extraction code.
- Transient network failures are reported. The caller may retry; no anti-bot bypass or proxy is provided.

### Pricing

Price: **US$0.02 per successful, non-demo document**, covering up to the fixed limits. One `document-processed` event is emitted only when the result has rows and no detected warning, all promised outputs have been saved, and pay-per-event is configured. Warning, empty, invalid and demo results have no developer event charge. There are no additional start or per-row fees. Platform usage is included in the document price. A budget check precedes storage; a storage failure has no document fee. If charging fails after storage, results can remain available without a successful charge; there is no automatic retry or duplicate charge.

The synthetic demo is free of developer event charges. Review the active price shown by Apify before starting a run.

### Verified examples and scope

The implementation passed 31 local normal, error and boundary tests. Six local SDK billing scenarios covered successful documents, warnings, demo, zero budget, insufficient budget and storage failure. These are tests, not purchases or customer endorsements. Cloud tests verified the original two-page catalog and a public aggregate statistics PDF. Testing does not establish accuracy for every PDF or recipe.

The demo produces these physical rows:

| Page | Code | Item | Quantity | Unit |
|---|---|---|---|---|
| 1 | A-001 | Paper clips | 120 | box |
| 1 | A-002 | Binder |  | pack |
| 2 | A-003 | Notebook | 25 | piece |
| 2 | A-004 | Labels | 0 | roll |

#### Tested public PDF input

This historical public aggregate statistics sample extracts only State and Permit, including the total row. It contains aggregate counts, not individual background-check records.

```json
{
  "useDemo": false,
  "pdfUrl": "https://raw.githubusercontent.com/jsvine/pdfplumber/stable/examples/pdfs/background-checks.pdf",
  "recipe": {
    "pageWidth": 1008,
    "pageHeight": 612,
    "top": 80,
    "bottom": 483,
    "boundaries": [
      33,
      111,
      147
    ],
    "columns": [
      "State",
      "Permit"
    ],
    "skipExactHeader": false,
    "rowTolerance": 1
  }
}
```

The tested recipe produced 56 physical rows. Selected visual spot checks and the sum of the 55 geographic rows matched the printed total 804,006. Text preserves the source's grouping spaces and commas; normalize numbers deliberately in your downstream workflow. This checks only the selected columns and does not verify every field in the PDF.

### Provenance

The implementation and synthetic catalog were written for this product. The original code is MIT licensed and uses pdfplumber and the official Apify SDK. Runtime dependencies retain their respective licenses. No private documents, competitor source code or paid model services are included.

# Actor input Schema

## `useDemo` (type: `boolean`):

Run a bundled public-domain-style synthetic catalog. Leave pdfUrl empty. Demo uses the bundled recipe unless you supply one.

## `pdfUrl` (type: `string`):

HTTPS URL on port 443, without login, query parameters or fragment. Public reports/catalogs only; no personal or confidential documents. Maximum 10 MiB and 50 pages.

## `recipe` (type: `object`):

Use PDF points measured from the top-left. columns: unique names; boundaries: one more x coordinate than columns; pageWidth/pageHeight; top/bottom. Optional skipExactHeader and rowTolerance. See README example.

## Actor input object example

```json
{
  "useDemo": true
}
```

# Actor output Schema

## `rows` (type: `string`):

One item per physical PDF text line, with named cells, source page and diagnostics.

## `report` (type: `string`):

Recipe, source checksum, page reports and complete rows. Review warnings before using the data.

## `csv` (type: `string`):

Named columns, page and row numbers. Formula-like text is prefixed for safer spreadsheet import.

## `summary` (type: `string`):

Document and page diagnostics without row text or source URL.

# 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 = {
    "useDemo": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("anchored_quarterstaff/pdf-column-recipe").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 = { "useDemo": True }

# Run the Actor and wait for it to finish
run = client.actor("anchored_quarterstaff/pdf-column-recipe").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 '{
  "useDemo": true
}' |
apify call anchored_quarterstaff/pdf-column-recipe --silent --output-dataset

```

## MCP server setup

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

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/iRHWs078fgXXNiquU/builds/k5BrW8mUQ7ZIyvlVn/openapi.json
