# PDF Toolkit: split, merge, extract pages, tables, OCR, redact (`praestat/pdf-toolkit`) Actor

One Actor for the common PDF jobs: extract or split pages, merge, rotate, render pages to images, pull text, extract tables as JSON, watermark, redact terms, compress and fill forms. Structured output, per-page pricing.

- **URL**: https://apify.com/praestat/pdf-toolkit.md
- **Developed by:** [praestat media](https://apify.com/praestat) (community)
- **Categories:** Automation, Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 processed pages

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## PDF Toolkit — extract pages, split, merge, tables, redact, compress

One Apify Actor for the everyday PDF jobs. No Ghostscript, no external service: everything runs
inside the actor container, results come back as structured dataset items plus downloadable files
in the key-value store.

### Operations

| `operation` | What it does | Output |
|---|---|---|
| `extract_pages` | Keep only the pages you select (`1-3,7,10-`, negative = from the end) | new PDF + page list |
| `split` | Split into single pages or every N pages | one PDF per part |
| `merge` | Merge several PDFs in the given order | one PDF |
| `rotate` | Rotate selected pages by 90/180/270° | new PDF |
| `text` | Text per page (fast, no layout reconstruction) | text + char count per page |
| `metadata` | Page count, document info, encryption flag | JSON |
| `extract_tables` | Tables via pdfplumber, returned as row/column JSON | `tables.json` |
| `to_images` | Render pages to PNG/JPEG at a chosen DPI | image files |
| `watermark` | Diagonal text watermark on selected pages | new PDF |
| `redact` | Remove terms from the text layer **and** black out the area | new PDF + hit list |
| `compress` | Strip duplicates, deflate streams, re-encode images as JPEG | smaller PDF + savings |
| `fill_form` | Fill AcroForm fields by name | new PDF + filled/missing field list |
| `markdown` | Convert pages to LLM-ready Markdown: headings, lists, tables, page anchors | `document.md` |
| `ocr` | Recognise text and word boxes on **scanned** pages — Tesseract runs inside the container, no external OCR service | `ocr.json` with text, word boxes and confidence per page |

### Input example

```json
{
  "operation": "extract_pages",
  "pdfUrls": ["https://example.com/invoice.pdf"],
  "pages": "1-2,5"
}
```

```json
{
  "operation": "redact",
  "pdfUrls": ["https://example.com/contract.pdf"],
  "redactTerms": ["Max Mustermann", "AT61 1904 3002 3457 3201"],
  "pages": ""
}
```

```json
{
  "operation": "extract_tables",
  "pdfUrls": ["https://example.com/report.pdf"],
  "pages": "2-6"
}
```

```json
{
  "operation": "markdown",
  "pdfUrls": ["https://example.com/report.pdf"],
  "pages": ""
}
```

```json
{
  "operation": "ocr",
  "pdfUrls": ["https://example.com/scan.pdf"],
  "pages": "1-3",
  "ocrLanguage": "eng+deu",
  "ocrDpi": 300
}
```

### Output

Every run pushes one item per operation to the dataset:

```json
{
  "operation": "extract_pages",
  "pages": [1, 2, 5],
  "pageCount": 3,
  "file": "out/extracted.pdf"
}
```

Generated files are also stored in the default key-value store (`OUTPUT` records plus the binary
files), so a follow-up actor or your own script can pick them up directly.

### Pricing (pay per event)

| Event | When | Price |
|---|---|---|
| `apify-actor-start` | once per run and per GB of memory (2 GB default) | $0.005 |
| `page-processed` | per page in the result | $0.002 |
| `image-rendered` | per rendered page image | $0.001 |
| `table-extracted` | per table found | $0.02 |
| `page-redacted` | per page that contained a match | $0.01 |
| `form-filled` | per filled form field | $0.002 |
| `page-ocred` | per page run through OCR (replaces `page-processed` for this operation) | $0.01 |

A 10-page extract therefore costs about $0.03; a 3-table report extraction about $0.08.
OCR is the expensive operation: recognition needs roughly a second of CPU per page, so an OCR page
is charged at $0.01 and `page-processed` is **not** charged on top of it.
The start event is charged once per GB of memory, so the 2 GB default costs $0.01. The code charges the remaining events.

### Limits and honest notes

- `text` reads the PDF text layer. **Scanned** pages have none — use `ocr` for those instead.
- `markdown` is a heuristic, not a layout engine: headings come from font size, lists from bullet
  characters, tables are appended per page as pipe tables, and the reading order follows the
  position on the page. Multi-column layouts will come out interleaved — check the output before
  feeding it into a pipeline.
- `ocr` uses Tesseract with English and German models. It is accurate on clean scans and printed
  text, weaker on handwriting, unusual fonts and rotated pages. The confidence per word is in the
  output so you can see where it struggled instead of trusting a single number.
- OCR is CPU work: expect roughly 1–2 seconds per page at 200 DPI on a 2 GB run. For a few hundred
  pages, raise the memory setting — the `apify-actor-start` price scales with it.
- **Which DPI?** `ocrDpi` defaults to 200, and that is measured, not guessed: on a clean two-page
  scan, 150 / 200 / 300 DPI produced the *same text* in 17.6 s / 23.6 s / 31.9 s. Higher resolution
  does not improve clean input, it only costs runtime. Use 300 when the source is noisy, faxed or a
  poor photocopy — there the extra pixels do help.
- `redact` removes the matched text from the content stream and paints a filled box. It is not a
  compliance-grade redaction suite: hidden metadata, annotations or attached files are not scrubbed.
- `compress` re-encodes embedded images; scanned PDFs shrink a lot, text PDFs only slightly.
- Encrypted PDFs are rejected with a clear error (no password support yet).
- Max input size is limited by the Apify memory setting of the run.

### Reporting a problem

Please use the **Issues** tab on the Actor page instead of email. The report then sits with the
right Actor, together with the input and the run it came from — usually exactly what is missing to
reproduce it. Issues are read and answered there.

### What happens to your data

Input files and results stay in your run's storage on Apify. Apify keeps the ten most recent runs of
an Actor indefinitely; older runs and their default storages are deleted under the platform's
retention rules. I do not export or archive your files anywhere else, and the Actor never sends them
to a third-party service.

### Local development

```bash
python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python -m pytest tests -q
echo '{"operation":"extract_pages","pdfUrls":["/path/file.pdf"],"pages":"1-2"}' > /tmp/in.json
.venv/bin/python -m src --input /tmp/in.json --outdir out --push
```

The CLI path is the same code as the actor run, so what passes locally behaves identically on Apify.

# Actor input Schema

## `operation` (type: `string`):

Was mit dem PDF passieren soll.

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

URLs oder lokale Pfade zu PDFs. 'merge' verarbeitet alle, alle anderen Operationen das erste.

## `pages` (type: `string`):

Seitenauswahl wie 1-3,7,10- (leer = alle Seiten). Negative Werte zählen vom Ende.

## `everyNPages` (type: `integer`):

Nur für split: alle N Seiten ein Teil. 0 = jede Seite einzeln.

## `degrees` (type: `integer`):

Clockwise rotation in degrees (multiple of 90: 90, 180, 270).

## `dpi` (type: `integer`):

Render resolution. 150 is enough for clean digital PDFs, 200 (default for OCR) balances speed and accuracy, 300 for scans and small print.

## `format` (type: `string`):

Output image format when rendering pages (jpg or png).

## `watermarkText` (type: `string`):

Text stamped across the page, e.g. CONFIDENTIAL or a customer name.

## `opacity` (type: `number`):

Watermark opacity from 0 (invisible) to 1 (solid). 0.15 keeps the text readable without hiding content.

## `redactTerms` (type: `array`):

Diese Begriffe werden geschwärzt (Textstelle + Fläche).

## `imageQuality` (type: `integer`):

JPEG quality 40-95 for compressed output. Lower means smaller files and more visible artifacts.

## `formValues` (type: `object`):

Nur für fill_form: Feldname → Wert.

## `ocrLanguage` (type: `string`):

Nur für ocr: Sprachmodell. eng, deu oder beide kombiniert (eng+deu).

## `ocrDpi` (type: `integer`):

Nur für ocr: Auflösung, mit der die Seite vor der Erkennung gerendert wird. Gemessen bei sauberem Scan: 150/200/300 liefern denselben Text (17,6/23,6/31,9 s für zwei Seiten) — 200 ist der Standard. 300 lohnt bei verrauschten, gefaxten oder schlecht kopierten Vorlagen; höher heißt langsamer.

## Actor input object example

```json
{
  "operation": "extract_pages",
  "pdfUrls": [
    "https://example.com/sample.pdf"
  ],
  "pages": "",
  "everyNPages": 0,
  "degrees": 90,
  "dpi": 150,
  "format": "png",
  "watermarkText": "CONFIDENTIAL",
  "opacity": 0.15,
  "imageQuality": 60,
  "ocrLanguage": "eng",
  "ocrDpi": 200
}
```

# Actor output Schema

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

One item per operation: page counts, table and character counts, redaction hits, produced file.

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

PDFs, images and JSON files produced by the run, stored under the prefix file-.

# 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://example.com/sample.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("praestat/pdf-toolkit").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://example.com/sample.pdf"] }

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

```

## MCP server setup

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

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/AOcssedElqpCX4dNX/builds/GSqdzSwPOk7ibQIbi/openapi.json
