# PDF to Text and OCR: Extract Text from PDFs and Scans (`boubap/pdf-text-ocr`) Actor

Extract the text of PDF files and scanned images, one row per page. Digital pages are read from their text layer at $0.50 per 1,000 pages, scans go through Tesseract OCR at $4 per 1,000 pages in 7 languages. Failed and blank pages are free.

- **URL**: https://apify.com/boubap/pdf-text-ocr.md
- **Developed by:** [Baptiste Bouton](https://apify.com/boubap) (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

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/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 to Text and OCR: Extract Text from PDFs and Scans

One row per page: text, method used, word count and, for scanned pages, OCR confidence. Pages of digital PDFs are read from their text layer, which is exact and fast. Scanned pages and images go through Tesseract OCR, in English, French, German, Spanish, Italian,
Portuguese or Dutch. You pay per page, never per document, and failed pages are free.

### What it does

- **Digital PDFs**: the text layer is extracted page by page with poppler (`pdftotext`), line breaks kept.
- **Scanned PDFs and images** (PNG, JPEG, TIFF, BMP, GIF, WebP): each page is rendered at the resolution of its scan
  (150 to 300 dpi) and read by Tesseract 5. With `ocrMode` set to `auto`, OCR runs only on pages without a text layer,
  so a PDF that mixes typed and scanned pages costs OCR only for the scanned ones. A multipage TIFF gives one row per
  page, like a PDF.
- **Files from your Apify account**: pick a key-value store in `keyValueStoreId` and every file in it is read. The
  Actor gets read access to that store only.
- The text is returned as it is. Nothing is extracted from it: no names, no amounts, no IBAN, no passport or ID card
  zone. Use the text in your own pipeline (search index, translation, LLM, archive).

### Use cases

- Make a set of scanned reports, books or archives searchable.
- Feed the text of PDF documents to an LLM or a RAG index, page by page with the page number kept.
- Check which PDFs of a collection are scans without a text layer (`method` = `ocr`).
- Extract the text of public documents (laws, tenders, annual reports, tax guides) in bulk.

### Not for identity documents or health records

Do not send ID cards, passports, driving licences, payslips, bank statements or medical documents. The Actor does not
recognize or extract any field, and documents you send may contain names of people: you are responsible for having
the right to process them. Downloaded files are deleted as soon as their pages are read, and nothing is kept outside
the dataset of your run.

### Input

| Field | Description | Default |
|---|---|---|
| `sources` | Public URLs of PDF files or images, one per line. The type is read from the file content | |
| `keyValueStoreId` | Optional key-value store of your account holding the files (every record except `INPUT`) | |
| `ocrMode` | `auto`: OCR only for pages without a text layer. `always`: OCR every page. `never`: text layer only | `auto` |
| `ocrLanguages` | Languages of the scanned text, up to 3: `eng`, `fra`, `deu`, `spa`, `ita`, `por`, `nld` | `["eng"]` |
| `maxPages` | Page rows written in the run, over all files, failed and blank pages included | `1000` (up to 100,000) |
| `maxPagesPerFile` | Only the first pages of longer files are read | `500` (up to 5000) |

Up to 1,000 files per run, 100 MB per file and 50 megapixels per image page (about 7,000 x 7,000 pixels); larger
images are refused with a free error row. A file whose download takes more than 5 minutes is reported as too slow. Only
public addresses are fetched: URLs that point to a private or local network are refused.

Example:

```json
{
  "sources": [{ "url": "https://www.irs.gov/pub/irs-pdf/p15.pdf" }],
  "ocrLanguages": ["eng"],
  "maxPagesPerFile": 20
}
```

### Output

One row per page. Real rows from a run on IRS Publication 15 and on a scanned page of Jules Verne's
*De la Terre à la Lune* (1865 edition, public domain) on 28 October 2026, text shortened here:

```json
[
  {
    "sourceUrl": "https://www.irs.gov/pub/irs-pdf/p15.pdf",
    "fileName": "p15.pdf",
    "fileType": "pdf",
    "page": 2,
    "pageCount": 59,
    "method": "text-layer",
    "ocrLanguages": null,
    "ocrConfidence": null,
    "wordCount": 973,
    "charCount": 6331,
    "text": "the employer and employee. The social security wage\nbase limit is $184,500.\nThe Medicare tax rate is 1.45% each for the employee\nand employer, unchanged from 20...",
    "error": null
  },
  {
    "sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Verne_-_De_la_Terre_%C3%A0_la_lune.djvu/page19-1920px-Verne_-_De_la_Terre_%C3%A0_la_lune.djvu.jpg",
    "fileName": "page19-1920px-Verne_-_De_la_Terre_à_la_lune.djvu.jpg",
    "fileType": "image",
    "page": 1,
    "pageCount": 1,
    "method": "ocr",
    "ocrLanguages": "eng+fra",
    "ocrConfidence": 92.4,
    "wordCount": 343,
    "charCount": 2222,
    "text": "EFFET DE LA COMMUNICATION BARBICANEF. 15\n\n—————\n\nhaletantes; puis il se fit un moment de silence, semblable à ce calme\nprofond qui précède les coups de tonnerre. E...",
    "error": null
  }
]
```

A file that cannot be read gives one row with `page` = null and the reason in `error` (HTTP 404, not a PDF or an image,
password-protected PDF, larger than 100 MB). A page that fails gives a row with its `page` and `error`. These rows
are free.

### Accuracy

Measured on 28 October 2026 on 20 pages, with the share of the words of a reference text found in the OCR output:

| Pages | Words right |
|---|---|
| 10 pages of digital PDFs (IRS Publication 15 with 2 table pages, French Constitution) rendered as 200 dpi scans | 97.7 % |
| 5 book scans from Wikisource (English and French), compared with the proofread transcription | 97.8 % |
| The same kind of scans degraded to about 150 dpi and tilted by 2.5 to 3 degrees | 97.4 % |

Over the 10 real scans (5 clean, 5 degraded) the score is 97.6 %. Expect less on photos taken with a phone, handwriting (not supported), very small print and complex tables, whose
cells come out as lines of text without the grid.

### Pricing

- $0.0005 per page read from the text layer ($0.50 per 1,000 pages).
- $0.004 per page read by OCR ($4 per 1,000 pages).

Pages that fail and blank pages are free. After 10 pages in a row where OCR finds no text, the Actor stops reading
that file and says so in an error row, so a blank or unreadable file does not use up run time. There is no fee per
file or per run start, and volume discounts apply on
higher Apify plans. Set a maximum cost per run in the Apify Console: the Actor checks it before each OCR page and
stops cleanly, without charging pages it did not read.

### Run time

The default memory of 2 GB reads about 4 to 6 OCR pages per minute; text-layer pages take a fraction of a second.
OCR reads one page at a time on one CPU core, so more memory does not make it faster: 2 GB is enough, and the
maximum is 4 GB. The run stops one minute before its timeout (2 hours by
default) and ends normally with the pages read so far. If the platform moves the run to another server, it resumes
there without charging the same pages twice.

### Integrations and recipes

- **Apify MCP server**: let an AI assistant call this Actor to read a PDF or a scan and get the text page by page.
- **Make, Zapier, n8n**: start a run from a new file in Google Drive or an e-mail attachment URL, then read the
  dataset items to index, translate or summarize the text.

### FAQ

**Do I need an API key?** No. OCR runs inside the Actor with Tesseract; nothing is sent to a third-party service.

**Which languages?** English, French, German, Spanish, Italian, Portuguese and Dutch, up to 3 per run.

**Does it keep my files?** No. Downloaded files are deleted as soon as their pages are read.

**Does it read handwriting, invoices or ID cards?** No. Handwriting is not supported, and no field is extracted
(no amounts, no names). Do not send identity documents, payslips, bank statements or health records.

# Actor input Schema

## `sources` (type: `array`):

Public URLs of PDF files or images (PNG, JPEG, TIFF, BMP, GIF, WebP), one per line. The type is read from the file itself, whatever the URL. Up to 1000 files and 100 MB per file. Do not send identity documents or health records.

## `keyValueStoreId` (type: `string`):

Optional: a key-value store of your Apify account holding PDF or image files. Every record of the store is read (except INPUT), after the URLs above. The Actor only gets read access to this store.

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

Auto reads the text layer of digital PDFs and runs OCR on scanned pages only. Use Always when the text layer of a PDF is wrong or garbled. Images always need OCR.

## `ocrLanguages` (type: `array`):

Languages of the scanned text, up to 3. Pick the languages actually present: each extra one slows OCR down. Default English.

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

Stops the run after this many pages over all files. Default 1000, up to 100000.

## `maxPagesPerFile` (type: `integer`):

Only the first pages of longer files are read. Default 500, up to 5000.

## Actor input object example

```json
{
  "sources": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/p15.pdf"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Verne_-_De_la_Terre_%C3%A0_la_lune.djvu/page19-1920px-Verne_-_De_la_Terre_%C3%A0_la_lune.djvu.jpg"
    }
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng",
    "fra"
  ],
  "maxPages": 1000,
  "maxPagesPerFile": 3
}
```

# Actor output Schema

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

Dataset items of the run: one item per page, with the fields described in the dataset schema.

# 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 = {
    "sources": [
        {
            "url": "https://www.irs.gov/pub/irs-pdf/p15.pdf"
        },
        {
            "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Verne_-_De_la_Terre_%C3%A0_la_lune.djvu/page19-1920px-Verne_-_De_la_Terre_%C3%A0_la_lune.djvu.jpg"
        }
    ],
    "ocrLanguages": [
        "eng",
        "fra"
    ],
    "maxPagesPerFile": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("boubap/pdf-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 = {
    "sources": [
        { "url": "https://www.irs.gov/pub/irs-pdf/p15.pdf" },
        { "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Verne_-_De_la_Terre_%C3%A0_la_lune.djvu/page19-1920px-Verne_-_De_la_Terre_%C3%A0_la_lune.djvu.jpg" },
    ],
    "ocrLanguages": [
        "eng",
        "fra",
    ],
    "maxPagesPerFile": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("boubap/pdf-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 '{
  "sources": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/p15.pdf"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Verne_-_De_la_Terre_%C3%A0_la_lune.djvu/page19-1920px-Verne_-_De_la_Terre_%C3%A0_la_lune.djvu.jpg"
    }
  ],
  "ocrLanguages": [
    "eng",
    "fra"
  ],
  "maxPagesPerFile": 3
}' |
apify call boubap/pdf-text-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,boubap/pdf-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/IPj2gPx25QVyalcu9/builds/3reTsz8wv0izxbQ92/openapi.json
