# Image & Scanned PDF OCR: Image to Text (Tesseract) (`wrentools/scanned-pdf-image-ocr`) Actor

Image to text: reads the text in images, screenshots and scanned PDFs with Tesseract OCR, offline: no AI, no third-party API. Plain text, per-page confidence, and optional words with bounding boxes, in 27 languages. Respects robots.txt and PDF permissions. Failed files are free.

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

## Pricing

$3.00 / 1,000 page read (ocr)s

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

## Image & Scanned PDF OCR: Image to Text (Tesseract)

Image to text: read the text in photos, screenshots, scanned letters, receipts, forms and scanned PDFs. Give it links to image or PDF
files, or send files inline as base64, and get back for each one: the text, line by line, a confidence score per page,
and optionally every word with its position on the page.

Everything runs inside the Actor with [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) and its
[tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast) language models (both Apache-2.0). Your files are not
sent to any AI model or third-party API, and the result for the same file is the same every time.

If your PDFs already have a text layer (they were made by a word processor, not a scanner), the
[PDF to Text & Markdown Extractor](https://apify.com/wrentools/pdf-text-markdown-extractor) reads them faster and for
less. Its `pagesWithoutText` field tells you which pages need OCR.

**Code examples**, in Python and Node, each run against this Actor before publishing:
[scanned PDFs and images from links](https://github.com/WrenTools/apify-actor-examples/tree/main/ocr-scanned-pdfs-and-images),
and [a folder of screenshots into one searchable CSV](https://github.com/WrenTools/apify-actor-examples/tree/main/text-from-a-folder-of-screenshots)
(the files are sent inline, so they need not be online).

### What you get

- **Text** for each file, one line per printed line, with a blank line between paragraphs and blocks.
- **Confidence**: `meanConfidence` (0-100, weighted by characters) for the file and for each page, and
  `lowConfidencePages` listing pages below 60, so you can send those for a human look.
- **Per-page results** (`includePages`): text, confidence and the page's pixel size.
- **Words with bounding boxes** (`includeWords`): each word's text, confidence, `left`, `top`, `width` and `height` in
  pixels, for parsing invoices and forms by position.
- **27 languages**: English, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Czech, Swedish, Danish,
  Norwegian, Finnish, Turkish, Romanian, Hungarian, Greek, Russian, Ukrainian, Vietnamese, Indonesian, Japanese, Chinese
  (simplified and traditional), Korean, Arabic and Hindi. Up to 3 per run.
- **Files**: PNG, JPEG, TIFF (every page of a multi-page TIFF), GIF, BMP, WebP and PDF (every page rendered at
  `pdfDpi`). Photos are turned upright from their EXIF orientation; transparent images are read on white.
- `pagesWithoutText` lists pages where no text was found, and `downscaledPages` lists images over 40 megapixels that
  were scaled down before reading.

Failed files are reported in the dataset with a reason and are **not charged**: `fetch-failed`, `file-too-large`,
`unsupported-file`, `unreadable-image`, `image-too-large`, `unreadable-pdf`, `robots-disallowed`, `password-required`,
`extraction-not-permitted`, `ocr-timeout`, `ocr-failed`, `invalid-base64`.

### Respectful by design

- **Only the files you name are downloaded**, one request each. The site's robots.txt is read first and obeyed, and
  downloads from one site are spaced (500 ms by default). No proxy, no browser, no login.
- **PDF permissions are honoured.** A PDF that needs a password is refused (the Actor takes no password), and so is a
  PDF whose owner has forbidden copying or extracting its text: reading its pixels back would be the same extraction.
- Only public internet addresses are fetched: a URL or redirect into a private, loopback or link-local network is
  refused.

Process only documents you have the right to use.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `fileUrls` | string\[] | | links to images or PDFs |
| `filesBase64` | array | | base64 strings, or `{"name": "scan.png", "data": "<base64>"}`; a `data:` prefix is fine |
| `languages` | string\[] | `["eng"]` | 1 to 3 codes, e.g. `["eng", "deu"]` |
| `pageSegmentation` | string | `auto` | `auto`, `singleColumn`, `singleBlock` (receipts, labels), `sparse` (forms, photos) |
| `includePages` | boolean | `false` | per-page text and confidence |
| `includeWords` | boolean | `false` | words with bounding boxes (adds `pages` too) |
| `pdfDpi` | integer | `300` | 72 to 600; raise it for small print |
| `maxPagesPerFile` | integer | `100` | longer files are marked `truncated` |
| `maxFileSizeMb` | integer | `50` | larger downloads stop, uncharged |
| `requestDelayMs` | integer | `500` | pause between downloads from one site |

Give `fileUrls`, `filesBase64` or both. Results come back in input order.

### Output

One dataset item per file (this is the Store example: a tilted, speckled phone scan of a notice):

```json
{
  "source": "https://example.com/scans/notice.png",
  "url": "https://example.com/scans/notice.png",
  "finalUrl": "https://example.com/scans/notice.png",
  "fileName": "notice.png",
  "status": "ok",
  "error": null,
  "fileType": "image",
  "pageCount": 1,
  "pagesProcessed": 1,
  "truncated": false,
  "meanConfidence": 96.4,
  "pagesWithoutText": [],
  "lowConfidencePages": [],
  "downscaledPages": [],
  "charCount": 194,
  "wordCount": 31,
  "text": "NOTICE OF SCHEDULED MAINTENANCE\nWater service on Alder Street will be off\n\non Tuesday 4 November from 9:00 to 13:00.\n..."
}
```

A failed file has `"status": "error"` and `"error": {"code": "password-required", "message": "..."}`. If a result is
too large for one dataset item (over 8 MB), its `text` and `pages` are stored in the run's key-value store, and the
item carries `fullOutputKey` and `fullOutputUrl`. The run's `SUMMARY` record counts files read and failed, and pages
read.

### Pricing

Pay per event, no start fee, and nothing for files that fail:

| Event | Price | Per 1,000 |
|---|---|---|
| `page-ocr` (one image, or one page of a PDF or TIFF, read) | $0.003 | $3.00 |

A single image costs $0.003; a 20-page scanned PDF costs $0.06. When you set a maximum charge for a run, the Actor
stops before a file it cannot pay for, and reads only the pages the remainder covers (the item is marked `truncated`).

### Limits

- Tesseract reads printed text well and handwriting poorly. Low-contrast photos, heavy skew and decorative fonts lower
  the confidence; `lowConfidencePages` shows where.
- Tables come out as text lines; their cell structure is not rebuilt. Use `includeWords` positions to rebuild columns.
- A page that takes over 180 seconds is stopped and reported as `ocr-timeout`. Very large pages are scaled down to 40
  megapixels first.

# Actor input Schema

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

Links to images (PNG, JPEG, TIFF, GIF, BMP, WebP) or PDFs you are allowed to process. Each file is downloaded once; the site's robots.txt is respected.

## `filesBase64` (type: `array`):

Files sent inline, for files that are not online: a list of base64 strings, or of objects {"name": "scan.png", "data": "<base64>"}. A data: URL prefix is accepted.

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

The languages printed in the files (at most 3; each one added slows every page). Use the fewest that cover the text.

## `pageSegmentation` (type: `string`):

How the text is laid out. Automatic suits most pages; try 'sparse' for forms and photos.

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

Add a 'pages' list with each page's text, confidence and size.

## `includeWords` (type: `boolean`):

Add each word with its confidence and bounding box (pixels) to the 'pages' list, for layout-aware parsing.

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

PDF pages are rendered at this resolution before OCR. 300 suits most scans; raise it for small print.

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

Read at most this many pages of each PDF or multi-page TIFF; longer files are marked truncated. Pages are charged as read.

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

Larger downloads are stopped and reported as file-too-large, without charge.

## `requestDelayMs` (type: `integer`):

Minimum pause between two downloads from the same site.

## Actor input object example

```json
{
  "fileUrls": [
    "https://api.apify.com/v2/key-value-stores/uM2zEXblvgf0s1cES/records/scanned-notice.png"
  ],
  "languages": [
    "eng"
  ],
  "pageSegmentation": "auto",
  "includePages": false,
  "includeWords": false,
  "pdfDpi": 300,
  "maxPagesPerFile": 100,
  "maxFileSizeMb": 50,
  "requestDelayMs": 500
}
```

# 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://api.apify.com/v2/key-value-stores/uM2zEXblvgf0s1cES/records/scanned-notice.png"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wrentools/scanned-pdf-image-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://api.apify.com/v2/key-value-stores/uM2zEXblvgf0s1cES/records/scanned-notice.png"] }

# Run the Actor and wait for it to finish
run = client.actor("wrentools/scanned-pdf-image-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://api.apify.com/v2/key-value-stores/uM2zEXblvgf0s1cES/records/scanned-notice.png"
  ]
}' |
apify call wrentools/scanned-pdf-image-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wrentools/scanned-pdf-image-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/OLoGy5xvAvCaVNw2l/builds/TV9I2sNQ79dFPWvnc/openapi.json
