# PDF Text Extractor & OCR - Bulk PDF and Image to Text (`tinyrex/pdf-ocr-text-extractor`) Actor

Extract text from PDFs and images in bulk: text layer read exactly, OCR (Tesseract, 8 languages) only on scanned pages. $1.50 per 1,000 PDFs + $0.002 per OCR page; failed downloads and blank pages are free.

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

## Pricing

from $1.20 / 1,000 pdf extracteds

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 Text Extractor & OCR: bulk PDF and image to text

**Turn a list of PDF or image links into clean text for search, RAG, data entry or archiving** — the Actor reads each PDF's own text layer exactly (fast, no OCR errors) and runs Tesseract OCR only on scanned pages and images (PNG, JPEG, TIFF, WebP, GIF, BMP), in 8 languages. You get one row per file with the full text, page counts, method used and PDF metadata. **$1.50 per 1,000 PDFs + $0.002 per OCR page; failed downloads, non-PDF links, password-protected files and blank pages are free.** No API key, no login, no outside OCR service: everything runs inside the Actor.

### What you get from the PDF Text Extractor & OCR

- **Exact text from digital PDFs** — reports, invoices, forms, manuals and 100+ page publications are read from the text layer in seconds (a 142-page IRS publication took ~5 s), in natural reading order or with the physical layout kept for tables and columns.
- **OCR only where it's needed** — in `auto` mode, pages without a text layer (scans, faxes, photographed documents) and image files go through Tesseract OCR; mixed PDFs get both. You only pay the OCR price for pages OCR actually read.
- **Ready for AI and spreadsheets** — `text`, `words`, `pageCount`, `method` (`text-layer` / `ocr` / `mixed`), title, author and dates per file; optional per-page text for RAG chunking and page citations; a clear `error` on every file that failed (free).

### Sample output (JSON)

Real row from a run on a scanned (image-only) PDF:

```json
{
  "url": "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf",
  "status": "ok",
  "fileType": "pdf",
  "method": "ocr",
  "pageCount": 1,
  "pagesProcessed": 1,
  "textLayerPages": 0,
  "ocrPages": 1,
  "blankPages": 0,
  "ocrLanguages": ["eng"],
  "producer": "libtiff / tiff2pdf - 20150912",
  "createdAt": "2016-01-19T12:38:47.000Z",
  "chars": 4508,
  "words": 728,
  "text": "The LinnSequencer\n32 Track MIDI Sequence Recorder\n\nThe LinnSequencer is a state-of-the-art composition and performance tool for the professional music ...",
  "textTruncated": false,
  "charged": { "documents": 0, "ocrPages": 1 },
  "httpStatus": 200,
  "fileSizeBytes": 103856,
  "extractedAt": "2026-10-19T07:28:29.277Z"
}
```

A digital PDF looks like `"method": "text-layer", "pageCount": 6, "textLayerPages": 6, "title": "Form W-9 ...", "words": 6279, "charged": { "documents": 1, "ocrPages": 0 }`. With **Include per-page text** on, each row also has `pages: [{ "page": 1, "method": "ocr", "chars": 1110, "text": "..." }, ...]`.

Export as **CSV, Excel, JSON or XML**, or send results to Google Sheets, Make, Zapier, n8n, LangChain/LlamaIndex loaders, webhooks or the Apify API. A `SUMMARY` record in the key-value store has run totals.

### How to configure the PDF Text Extractor & OCR

1. Paste **PDF or image URLs**, one per line (direct links that download the file).
2. Or pick a **dataset** from another Actor run: every link ending in .pdf/.png/.jpg/... is collected, or set **URL field** to the column that holds your links.
3. Choose **OCR mode** and **OCR languages** if your scans aren't in English. Run.

#### Example input

```json
{
  "fileUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf"
  ],
  "ocrMode": "auto",
  "languages": ["eng"],
  "includePages": false,
  "maxPagesPerFile": 100
}
```

| Setting | Default | Notes |
|---|---|---|
| fileUrls | — | Direct links to PDFs or images; duplicates removed |
| datasetId / urlField | — | Read links from another run's dataset |
| ocrMode | auto | `auto` = OCR only pages without text; `always`; `never` (no OCR charges) |
| languages | eng | eng, deu, fra, spa, ita, por, nld, pol (pick only what you need) |
| ocrDpi | 200 | Render resolution for scanned pages; 300 for tiny print |
| includePages | false | Adds per-page text array |
| keepLayout | false | Keep columns/tables aligned with spaces |
| maxPagesPerFile | 100 | First N pages per PDF (up to 5,000) |
| maxFileSizeMb | 50 | Bigger files are skipped (free) |
| maxFiles | 1000 | Unique URLs per run (up to 10,000) |

**Speed:** digital PDFs take well under a second per file after download. OCR takes about 3–5 s per page at the default 4 GB memory (1 CPU core); give the run 8–16 GB to OCR several pages in parallel.

### Pricing

Pay per event, with Bronze/Silver/Gold discounts:

| Event | Price | When |
|---|---|---|
| PDF extracted | **$0.0015** ($1.50 / 1,000) | Once per PDF whose own text layer gave text, any page count up to your limit |
| OCR page | **$0.002** ($2 / 1,000) | Per scanned page or image that OCR read text from |
| Free | $0 | Dead links, HTTP errors, web pages, unsupported files, password-protected PDFs, blank pages, files over the size limit |

Example: 1,000 digital PDFs = $1.50. A 10-page scanned contract = $0.02. A tiny Actor-start fee applies per run. Set a max charge per run and the Actor stops cleanly when it's reached.

### FAQ

**How do I extract text from a scanned PDF?** Add its link and leave OCR mode on `auto`. Pages with no text layer are rendered to an image and read by Tesseract OCR; the row shows `"method": "ocr"` and `ocrPages`. For non-English scans, choose the language(s) under *OCR languages*.

**How can I tell if a PDF is scanned or has selectable text?** Run it once: `textLayerPages` counts pages with real text, `ocrPages` counts pages that needed OCR. `method` sums it up as `text-layer`, `ocr` or `mixed`.

**Can I convert PDF to text for ChatGPT, RAG or a vector database?** Yes. Use `text` for whole documents or turn on *Include per-page text* to chunk by page and cite page numbers. Very long documents keep the first 1,000,000 characters inline and store the full text in the run's key-value store (`fullTextUrl`).

**Does it extract text from images like PNG or JPG?** Yes. Image links are OCR'd as one page each (PNG, JPEG, TIFF, WebP, GIF, BMP).

**Does it work with password-protected PDFs?** No. Encrypted PDFs that need a password are reported with `error: "password-protected PDF"` and are free.

**Are my documents sent to a third-party OCR API?** No. Download, text extraction (poppler) and OCR (Tesseract) all happen inside the Actor's container; results stay in your own Apify storage.

### Related actors

- [Sitemap Scraper](https://apify.com/tinyrex/sitemap-scraper) — list every URL of a site, then filter the PDF links and feed them here.
- [TED EU Tenders Scraper](https://apify.com/tinyrex/ted-tenders-scraper) — EU public tenders; extract the text of tender documents with this Actor.
- [Bulk Website Screenshot](https://apify.com/tinyrex/bulk-website-screenshot) — full-page screenshots of many URLs.
- [SEO On-Page Audit](https://apify.com/tinyrex/seo-on-page-audit) — titles, meta, headings and structured data of web pages.

# Actor input Schema

## `fileUrls` (type: `array`):

Direct links to PDF files or images (PNG, JPEG, TIFF, WebP, GIF, BMP), one per line. Duplicates are removed. Links that fail to download or are not a PDF/image are listed with the reason and are free.

## `datasetId` (type: `string`):

ID of an Apify dataset with file links, e.g. the output of another scraper. Without a URL field, every value that looks like a link to a .pdf or image file is collected.

## `urlField` (type: `string`):

Name of the dataset field that holds the file link (dot notation like documents.url works; arrays are expanded). Use it when your links don't end in .pdf.

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

auto = read the PDF's own text layer and OCR only pages without text (scanned pages) and images. always = OCR every page. never = text layer only, no OCR charges.

## `languages` (type: `array`):

Languages printed in your scanned documents. Pick only the ones you need: each extra language makes OCR slower.

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

Resolution used to render scanned PDF pages before OCR. 200 is fast and accurate for normal print; use 300 for small text.

## `includePages` (type: `boolean`):

Adds a pages array (page number, method text-layer/ocr/empty, text) to each row. Handy for RAG chunking and page citations.

## `keepLayout` (type: `boolean`):

Keeps columns and table alignment of text-layer pages with spaces (pdftotext -layout). Off = natural reading order.

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

Only the first N pages of each PDF are read. Remaining pages are reported in pagesNotProcessed.

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

Bigger files are skipped (free).

## `maxFiles` (type: `integer`):

Hard cap on unique URLs processed in this run.

## Actor input object example

```json
{
  "fileUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf"
  ],
  "ocrMode": "auto",
  "languages": [
    "eng"
  ],
  "ocrDpi": 200,
  "includePages": false,
  "keepLayout": false,
  "maxPagesPerFile": 100,
  "maxFileSizeMb": 50,
  "maxFiles": 1000
}
```

# Actor output Schema

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

One row per file URL.

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

Totals for the run.

# 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 = {
    "fileUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tinyrex/pdf-ocr-text-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 = { "fileUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tinyrex/pdf-ocr-text-extractor").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 '{
  "fileUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf"
  ]
}' |
apify call tinyrex/pdf-ocr-text-extractor --silent --output-dataset

```

## MCP server setup

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

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/tZBdUoZ94VSFNvpse/builds/rftz7ZOw8f9agDbBf/openapi.json
