# PDF & Image to Text (OCR) for AI agents (`benelux-signals/pdf-image-to-text-ocr`) Actor

Turn PDFs, scans and images into clean per-page text and Markdown for AI agents and RAG. Reads the PDF text layer first, local Tesseract OCR for scans (EN, NL, DE, FR). No external APIs.

- **URL**: https://apify.com/benelux-signals/pdf-image-to-text-ocr.md
- **Developed by:** [Benelux Signals](https://apify.com/benelux-signals) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 ocr pages

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/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 & Image to Text (OCR) for AI agents: PDF, scan and image to Markdown

**Turn PDFs, scanned documents and images into clean text and Markdown, page by page.** Give the Actor one or more file URLs (or upload files) and get one dataset row per page with the text, a Markdown version, the detected language, the OCR confidence and the method used. Digital PDFs are read straight from their **text layer** ($0.001 per page). Scanned pages and images go through **local Tesseract OCR** in English, Dutch, German or French ($0.006 per page).

Everything runs inside the Actor with open-source tools (poppler and Tesseract 5). Your documents are **never sent to an external OCR or AI API**. Failed downloads, unreadable files and empty pages are **free**.

### Who is this PDF to text Actor for?

- **AI agents and MCP clients** that need to read a PDF, invoice, contract or screenshot inside a tool call. Every field has a safe default, and a call with only `urls` works.
- **RAG and LLM pipelines** that need per-page text or Markdown chunks with page numbers for citations.
- **Back-office automation** (Make, Zapier, n8n) that extracts text from scanned letters, forms or receipts.
- **Researchers and journalists** working with government PDFs, scanned archives and multilingual Benelux/DACH documents.

### What does it do?

1. **Downloads** each PDF or image (PDF, PNG, JPG, TIFF, GIF, WebP, BMP) from a URL, an uploaded file or a key-value store.
2. **Checks every PDF page for a usable text layer.** Pages with real text are extracted directly: fast, exact and cheap.
3. **Runs OCR only where it is needed**: on scanned pages, pages that contain only images and on all image files. Pages are rendered at 150 dpi (configurable), and the language is detected automatically or set by you.
4. **Cleans the text** (hyphenation, whitespace, page artefacts) and builds simple Markdown with headings and paragraphs.
5. **Saves one row per page** plus one Markdown file per document in the key-value store, and a `RUN_SUMMARY` with totals.

### How to use it

1. Paste one or more file URLs in **PDF or image URLs**, or upload files, or pick a **key-value store** that holds your files.
2. Optional: choose the **OCR language** (auto, English, Dutch, German, French), a **page range** and page limits.
3. Click **Start**. A one-page PDF takes about 2–5 seconds.
4. Read the results in the **Overview** table, or download them as JSON, CSV or Excel. The full Markdown per document is linked in the run summary.

#### Input example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://github.com/ocrmypdf/OCRmyPDF/raw/main/tests/resources/typewriter.png"
  ],
  "ocrMode": "auto",
  "ocrLanguage": "auto",
  "ocrDpi": 150,
  "outputFormat": "both",
  "maxPagesPerDocument": 50,
  "maxPages": 200
}
```

**Files in a private key-value store:** select the store in **Key-value store (optional)** and, optionally, list the keys. Without keys, all PDF and image files in the store are processed. A signed record URL (`…/records/scan.pdf?signature=…`) also works in **PDF or image URLs**.

#### Output example (one row per page)

```json
{
  "status": "ok",
  "charged": true,
  "input": "https://github.com/ocrmypdf/OCRmyPDF/raw/main/tests/resources/typewriter.png",
  "fileName": "typewriter.png",
  "fileType": "png",
  "page": 1,
  "pageCount": 1,
  "method": "ocr",
  "ocrLanguage": "eng+nld+deu+fra",
  "language": "nl",
  "ocrConfidence": 92.6,
  "wordCount": 101,
  "text": "Linzensoep a la Waterman\n\nkh ons linzen\n\n3 liter water ...",
  "markdown": "## Linzensoep a la Waterman\n\nkh ons linzen ...",
  "processingMs": 3088,
  "error": null
}
```

`method` is `text-layer` or `ocr`. `status` is `ok`, `empty` (no text found, free) or `error` (free, with the reason in `error`, for example `HTTP 404 Not Found when downloading the file.`).

### Accuracy and speed

Measured in Apify cloud runs (October 2026) against the original text:

| Document | Method | Text match |
|---|---|---|
| German law text, 10 scanned pages | OCR, 150 dpi | 99.8% |
| German scan as JPG | OCR, language auto-detected | 99.7% |
| Dutch noisy scan (rotated, JPEG artefacts) | OCR, auto | 93.7% |
| Dutch government PDF, 6 pages | text layer | exact |

Text-layer pages take milliseconds. OCR takes about 3–5 seconds per page for short documents. For long scans at the default 1 GB memory it takes about 15–20 seconds per page. Choose more memory (4 GB) for roughly 4× faster OCR at the same page price.

**Tip:** set the OCR language when you know it. In auto mode the first OCR page of each document is read with all four language models.

### How much does it cost?

Pay per event, no subscription needed:

| Event | Free plan | Bronze | Silver | Gold+ |
|---|---|---|---|---|
| Text-layer page (`text-page-extracted`) | $0.001 | $0.0009 | $0.0008 | $0.0007 |
| OCR page or image (`ocr-page-processed`) | $0.006 | $0.0055 | $0.005 | $0.0045 |
| Actor start (per GB of memory) | $0.005 | $0.005 | $0.005 | $0.005 |

Examples on the Free plan: a 20-page digital PDF costs about $0.025, a 20-page scan about $0.125 and one screenshot about $0.011. Empty pages, failed pages and error rows are never charged. Use **Max pages per run** or Apify's maximum cost per run to cap spending. The run stops cleanly at your limit.

### FAQ

#### Does it work for scanned PDFs without a text layer?

Yes. Pages without usable text, or whose text layer is garbled, are rendered and read with OCR automatically. Use `ocrMode: "ocr-only"` to force OCR, or `"text-layer-only"` to never OCR.

#### Which languages are supported?

English, Dutch, German and French (Tesseract `fast` models). Other Latin-script languages often work reasonably with English, but accents may be lost.

#### Can it read tables, handwriting or forms?

It extracts the text of tables in reading order, but not as table cells. Handwriting is not supported. Clear printed text and typewriter text work well.

#### Is my data private?

Files are processed inside your own Apify run and are not sent to third-party services. Temporary files are deleted at the end of each document.

#### Password-protected PDFs?

Enter the password in **PDF password** (stored as a secret).

### Is it legal?

Extracting text from documents you own or may use is legal. You are responsible for having the right to process the files you submit, for example under copyright or the GDPR when documents contain personal data. The Actor does not bypass DRM or access controls.

### Related actors by benelux-signals

Feed documents from other sources into OCR:

- [EU Tender Radar](https://apify.com/benelux-signals/eu-tender-radar) – TED + TenderNed public tenders with keyword/CPV filters and only-new alerts.
- [EU & Dutch News Monitor](https://apify.com/benelux-signals/eu-news-rss-monitor) – keyword alerts across 80+ built-in Dutch, Belgian and EU news and official RSS feeds.

More tools from the same maintainer:

- [Benelux Trade Fair Exhibitors](https://apify.com/benelux-signals/benelux-trade-fair-exhibitors) – exhibitor lists from Dutch and Belgian trade fairs (Horecava, METSTRADE, Batibouw…) for B2B lead lists.
- [Bulk PageSpeed & SEO Audit](https://apify.com/benelux-signals/bulk-pagespeed-seo-audit) – Core Web Vitals, Lighthouse scores and on-page SEO checks for many URLs or a sitemap.
- [Career Site Jobs API](https://apify.com/benelux-signals/eu-career-site-jobs-api) – jobs straight from Greenhouse, Lever, Recruitee, Personio, Ashby and other company career sites.
- [Workday Jobs API](https://apify.com/benelux-signals/workday-jobs-api-europe) – jobs from any myworkdayjobs.com career site, with a European employer seed list.
- [Arbeitsagentur Jobs API](https://apify.com/benelux-signals/arbeitsagentur-jobs-api) – German job ads from the Bundesagentur für Arbeit Jobbörse.
- [Remote Jobs Aggregator](https://apify.com/benelux-signals/remote-jobs-aggregator) – Europe-friendly remote jobs from RemoteOK, We Work Remotely, Jobicy and more, deduplicated.
- [Technical SEO Site Crawler](https://apify.com/benelux-signals/technical-seo-site-crawler) – crawl a site for broken links, redirects, missing titles/meta, canonicals and noindex issues.

Questions or a source we should add? Open an issue on the Actor page.

# Actor input Schema

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

One public link per line to a PDF, PNG, JPG, TIFF, WebP, GIF or BMP file. Scanned PDFs, photos of documents and screenshots are OCR'd; PDFs with a text layer are read directly (fast, exact). The example PDF is skipped automatically when you also upload files.

## `files` (type: `array`):

Upload PDFs or images from your computer (stored in your Apify account and read only by this run). Combined with the URLs above.

## `keyValueStore` (type: `string`):

Pick one of your Apify key-value stores that holds PDFs or images (e.g. saved by another Actor or an AI agent). The Actor only gets read access to this store. Leave the keys below empty to process every record whose key ends in .pdf, .png, .jpg, .tif, .webp, .gif or .bmp.

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

Only process these record keys from the picked key-value store (one per line). Empty = all PDF/image records.

## `ocrMode` (type: `string`):

Auto uses the embedded PDF text when a page has enough real text and runs Tesseract OCR only on scanned or image pages. Images are always OCR'd.

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

Language model for OCR. Auto reads the first OCR page with all four models, detects the language and uses only that model for the rest of the document. Pick one language for the fastest, most accurate result.

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

Resolution used to render scanned PDF pages for OCR. 150 is fast and accurate for normal documents; use 200 or 300 for very small print or poor scans. Images are OCR'd at their own resolution.

## `minTextLayerChars` (type: `integer`):

In auto mode a page that contains images is OCR'd when its text layer has fewer letters/digits than this. Pages without any image always use their text layer.

## `outputFormat` (type: `string`):

Which text fields to fill per page. Markdown adds headings and lists, ideal for LLMs and RAG pipelines.

## `saveDocumentMarkdown` (type: `boolean`):

Also store the whole document as a single .md file in the run's key-value store (link in the run summary).

## `pageRange` (type: `string`):

Only process these pages of every document, e.g. 1-5,8,10- . Leave empty for all pages (up to the per-document limit).

## `maxPagesPerDocument` (type: `integer`):

Stop after this many pages of each document. Protects you from unexpectedly long files.

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

Total page limit for the whole run (all documents together). You never pay for more pages than this.

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

Files larger than this are skipped with a free error row.

## `requestTimeoutSecs` (type: `integer`):

Maximum time to download one file.

## `pdfPassword` (type: `string`):

Password for encrypted PDFs (used for every PDF in this run). Stored encrypted; leave empty for normal PDFs.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://github.com/ocrmypdf/OCRmyPDF/raw/main/tests/resources/francais.pdf"
  ],
  "files": [],
  "keyValueStoreKeys": [],
  "ocrMode": "auto",
  "ocrLanguage": "auto",
  "ocrDpi": 150,
  "minTextLayerChars": 50,
  "outputFormat": "both",
  "saveDocumentMarkdown": true,
  "pageRange": "",
  "maxPagesPerDocument": 50,
  "maxPages": 200,
  "maxFileSizeMb": 50,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

All pages in the 'overview' table view (file, page, method, language, OCR confidence, text).

## `runSummary` (type: `string`):

JSON summary (pages ok/empty/failed, text-layer vs OCR pages, charged count, per-document stats and Markdown file links) stored under RUN_SUMMARY.

# 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://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("benelux-signals/pdf-image-to-text-ocr").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://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("benelux-signals/pdf-image-to-text-ocr").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 '{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call benelux-signals/pdf-image-to-text-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,benelux-signals/pdf-image-to-text-ocr"
        }
    }
}
```

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/7TxX6TFpgOmoA4Hrn/builds/HEegWPR3V9jpHgHs7/openapi.json
