# Bulk OCR: Images & Scanned PDFs to Text (Tesseract) (`stellalytics/bulk-ocr`) Actor

Turn scanned PDFs and images (PNG, JPEG, TIFF, WebP) into text with the open-source Tesseract OCR engine: one row per page with words and confidence. 9 languages. PDFs that already contain text are read directly. Pay per page with text; blank pages are free.

- **URL**: https://apify.com/stellalytics/bulk-ocr.md
- **Developed by:** [Stellalytics](https://apify.com/stellalytics) (community)
- **Categories:** Developer tools, AI, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page reads

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

## Bulk OCR: Images & Scanned PDFs to Text (Tesseract)

Turn scanned PDFs and images into searchable text, many files at once. Give links to your files, or pick a key-value store you uploaded them to. You get **one row per page** with the text, the word count and the OCR confidence. It works in Google Sheets, a database, an LLM pipeline or the next Actor.

It uses [Tesseract](https://github.com/tesseract-ocr/tesseract), the open-source OCR engine (Apache-2.0), with Poppler for PDFs. There is no third-party API, no API key and no sign-up. Your files are read inside the run and nowhere else.

### What you get

- **Images:** PNG, JPEG, TIFF (multi-page too), GIF, BMP and WebP. Photos are turned upright from their EXIF orientation.
- **PDFs:** scanned PDFs are rendered at 300 DPI (or your choice) and read page by page. Pages that already contain text are read directly, which is exact and fast. You can turn that off.
- **9 languages:** English, German, French, Spanish, Italian, Portuguese, Dutch, Polish and Turkish. Mixed-language documents work: pick every language they use.
- **Per page:** `text` with lines and paragraphs kept, `words`, `confidence` (0 to 100, the engine's own average), `method` (`ocr` or `textLayer`), `page` and `pageCount`.
- **Errors per file, not per run:** a broken link, a damaged PDF or a file that is not an image gets one row with an `error` saying why, and the other files carry on.
- **SUMMARY record:** files, pages read, charged, blank, failed and skipped.

Sample: the two prefilled public test images (from the Tesseract project's test files) give 2 rows. Here is the start of the first:

```json
{
  "file": "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
  "page": 1,
  "pageCount": 1,
  "method": "ocr",
  "text": "This is a lot of 12 point text to test the\nocr code and see if it works on all types\nof file format.",
  "words": 60,
  "confidence": 96.2,
  "error": null
}
```

### Use cases

- **Scanned contracts, invoices and letters** to text for search, a spreadsheet or a document database.
- **Feed an LLM or a RAG index** with the text of scanned PDFs, one page per row so each chunk keeps its page number.
- **Archives and old print:** digitize scanned pages in bulk at a fixed price per page.
- **Chain Actors:** run it on files another workflow saves to a key-value store.

### How to use it

1. In **File links**, paste direct links to your images or PDFs, one per line. Or pick a **Key-value store** that holds them (for example, one you uploaded scans to in Apify Console).
2. Pick the **languages** in your documents.
3. Optionally set **Maximum pages per file** and **Maximum pages to read in this run** to cap the cost.
4. Run it. The text is in the Output tab, one row per page, and the counts are in the SUMMARY record.

Links must be direct downloads that anyone can open, such as a public cloud-storage link or a signed URL. Files are limited to 50 MB each.

### Pricing

You pay **per page that yields text**, plus Apify's small start fee; the current price is on the Pricing tab. Blank pages, pages that fail, files that can't be read and pages over your limits are never charged. Set **Maximum pages to read in this run**, or a maximum cost per run, to cap the cost.

### Accuracy

OCR is a best effort. Clean scans at 300 DPI usually read with confidence above 90. Low-resolution photos, handwriting, unusual fonts and complex tables read worse; check the `confidence` column. For small print, set the PDF resolution to 400 DPI. Handwriting is not supported.

### Automate it: schedules and integrations

- **Export and connect:** download as CSV, Excel or JSON, or send the result to Google Sheets, Slack, Make, Zapier or n8n through Apify's integrations.
- **API and MCP:** call it from the Apify API, or from an AI agent through the Apify MCP server.

### Limitations

- Use it only for files you have the right to process. It doesn't recognize what a document is and doesn't remove personal data from it: the text is whatever is on the page.
- Password-protected PDFs can't be read.
- Layout is simplified to lines and paragraphs; tables come out as text lines, not cells.
- At most 1,000 files per run and 2,000 pages per file.

### FAQ

**Do my files leave the run?**
They are downloaded into the run's container, read and deleted when the run ends. The text is written to the run's dataset in your account. Nothing is sent to a third-party service.

**Why is a page marked `textLayer`?**
The PDF page already contained text (it was not a scan), so that text was used instead of OCR. Turn off **Use a PDF's own text** to OCR it as an image anyway.

**Can it read a whole folder?**
Upload the files to a key-value store in Apify Console and pick that store. Every image and PDF record in it is read.

### Support

Found a bug or need a feature? Open an issue on the Issues tab, where fixes for broken runs are a priority. For a custom version or a question you'd rather not post publicly, email <stellalytics@gmail.com>. Never send API keys or passwords by email or in an issue.

# Actor input Schema

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

Direct links to your images (PNG, JPEG, TIFF, GIF, BMP, WebP) or PDFs, one per line. Each file is downloaded once. Use only files you have the right to process.

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

Optional: a key-value store whose image and PDF records should be read, for example one you uploaded scans to in Apify Console. The Actor only reads it.

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

Pick every language that appears. More languages make reading a little slower.

## `useTextLayer` (type: `boolean`):

PDF pages that already contain text (not scans) are read directly, which is exact and fast. Turn off to OCR every page as an image.

## `pdfDpi` (type: `integer`):

Resolution used to turn PDF pages into images before OCR. 300 suits most scans; use 400 for small print.

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

Later pages of longer files are skipped (and not charged).

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

Caps the cost: the run stops after this many charged pages. 0 means no limit (your maximum cost per run still applies).

## Actor input object example

```json
{
  "fileUrls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
    "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
  ],
  "languages": [
    "eng"
  ],
  "useTextLayer": true,
  "pdfDpi": 300,
  "maxPagesPerFile": 100,
  "maxPages": 0
}
```

# Actor output Schema

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

No description

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

No description

# 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://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
        "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stellalytics/bulk-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 = { "fileUrls": [
        "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
        "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("stellalytics/bulk-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 '{
  "fileUrls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
    "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
  ]
}' |
apify call stellalytics/bulk-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stellalytics/bulk-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/tYfpXpRxfgbwSi4Uh/builds/RLZ8Tf474M4dGwynq/openapi.json
