# Image to Text OCR: Images & Scanned PDFs, 24 Languages (`ctriolab/image-ocr`) Actor

Extract text from image URLs (PNG, JPG, WebP, TIFF, GIF) and scanned PDFs with open-source Tesseract OCR. 24 languages incl. Korean, Japanese, Chinese. Confidence, word boxes, dataset input. Blank pages and failed files are free.

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

## Pricing

from $1.05 / 1,000 page with texts

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Image to Text OCR: Images & Scanned PDFs, 24 Languages

Extract text from **images and scanned PDFs** with open-source **Tesseract OCR**. Give direct file URLs, files in an Apify key-value store, or a dataset from another Actor, and get one clean row per file: the text, an average confidence score, per-page results and, if you want, every word with its bounding box.

- **Inputs:** PNG, JPG, WebP, TIFF (every page), GIF and BMP (first frame), scanned PDFs (every page, or a page range). Direct URLs, an Apify key-value store, or a dataset from another Actor.
- **24 languages:** English, Korean, Japanese, Chinese (Simplified and Traditional), Spanish, German, French, Portuguese, Italian, Dutch, Polish, Russian, Ukrainian, Turkish, Arabic, Hindi, Vietnamese, Indonesian, Thai, Swedish, Czech, Greek, Hebrew. Mix several for multilingual documents.
- **Preprocessing:** grayscale, automatic 2x upscaling of small images, optional deskew (straightens scans tilted up to 10 degrees), transparent PNGs put on white, phone photo rotation (EXIF) fixed.
- **Fair billing:** you pay per page that actually contains text. **Blank pages, broken links, web pages and unreadable files are free** and listed with a reason.
- **Private:** nothing is sent to Google, OpenAI or any other service. OCR runs inside the Actor's container.

### How to use it

1. Paste image or PDF links into **Image or PDF URLs** (or pick a key-value store, or a dataset and the name of its URL field).
2. Choose the **languages** of the text. English is the default.
3. Click **Start**. Each file becomes one row in the dataset. Export it as JSON, CSV, Excel or read it by API.

Leave the input empty to try it on a built-in public domain book page.

### Output

```json
{
  "url": "https://example.com/scan.pdf",
  "fileName": "scan.pdf",
  "status": "ok",
  "fileType": "pdf",
  "languages": "eng",
  "text": "FEDERAL BUREAU OF INVESTIGATION\nMemorandum ...",
  "confidence": 91.4,
  "wordCount": 612,
  "characterCount": 3861,
  "totalPages": 3,
  "pageCount": 3,
  "billedPages": 3,
  "pages": [
    { "page": 1, "text": "...", "confidence": 92.1, "wordCount": 230, "width": 2550, "height": 3300 }
  ],
  "processingSeconds": 6.2,
  "processedAt": "2026-10-29T05:00:00+00:00"
}
```

- `status`: `ok` (text found), `blank` (no text, not charged) or `failed` (with `error`, not charged).
- `confidence`: Tesseract's average word confidence, 0 to 100. Below about 60 usually means a poor scan or the wrong language.
- With **Include word boxes** each page gets `words: [{ text, confidence, left, top, width, height, line }]` in original image pixels.
- The run's key-value store has a `SUMMARY` record with billed pages, blank pages and failed files.

### Read URLs from another Actor's dataset

Pick the dataset in **Or read URLs from a dataset** and set **URL field** (dotted paths such as `images.0.url` work). Every column of the source row is copied into the result row, so you can join the text back to your products, listings or documents. If a source column has the same name as one of ours, it is kept as `source_<name>`. Rows whose link fails still appear with `status: "failed"` so nothing goes missing.

In an integration after another Actor, use `{{resource.defaultDatasetId}}` as the dataset.

### Scheduled runs: only new files

Turn on **Only new files** and give a **Monitor name**. URLs read in earlier runs with the same name are skipped (not charged), so a daily schedule over a growing list only pays for new images.

### Tips for better results

- Use **Sparse text** layout for receipts, signs, photos and tables, **Single block** for screenshots and cropped text.
- Pick the right language. Korean, Japanese and Chinese text needs its language selected.
- For crooked phone scans, turn on **deskew**.
- PDFs that already contain a text layer (not scanned) are read faster and cheaper by our [PDF Text Extractor](https://apify.com/ctriolab/pdf-text-extractor).

### Pricing

Pay per event: **$0.0015 per page with text ($1.50 per 1,000 pages)** on the Free plan, down to $0.00105 on Gold and higher plans, plus a tiny Actor start fee ($0.00005 per GB of memory). Platform usage (compute) is paid by you; in our tests it was about $0.0004 per scanned PDF page and about $0.0005 for a one-image run at the default 2 GB memory. Blank pages and failed files are never charged. You can cap the cost of a run with the maximum cost per run setting; the Actor stops cleanly when it is reached.

### FAQ

**Does it handle handwriting?** Tesseract is made for printed text. Neat block letters often work, cursive handwriting does not.

**Which image size works best?** Text about 20 to 40 pixels high. Small images are upscaled automatically.

**Can I send a PDF with many pages?** Yes. Set **Max pages per file** (default 50) and an optional page range like `1-10, 15`.

**Is my data stored?** Files are deleted from the container after OCR. Results stay only in your run's storage.

### Your files

Only submit files you have the right to process. This Actor does not log in anywhere, does not use proxies and does not scrape websites. It reads exactly the files you link.

### Licenses

Tesseract OCR and its trained data are Apache-2.0. PDF rendering uses PDFium (BSD-3) through pypdfium2. The sample page is public domain (Biodiversity Heritage Library, Wikimedia Commons).

Questions or a feature request: help@ctriolab.com. Made by [Ctrio Lab](https://ctriolab.com).

# Actor input Schema

## `imageUrls` (type: `array`):

Direct links to images (PNG, JPG, WebP, TIFF, GIF, BMP) or scanned PDFs. The URL must return the file itself, not a web page. Leave empty to read a built-in public domain sample page.

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

Language(s) of the text. Pick more than one for mixed documents (slower). Default English.

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

A key-value store in your account that holds images or PDFs. Pick it here and list the record keys below.

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

Keys of the image or PDF records, for example receipt-01.jpg.

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

An Apify dataset (for example from a scraper run) with image or PDF links. Every column of the source row is kept in the result row. In an integration after another Actor use {{resource.defaultDatasetId}}.

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

Field that holds the link in each dataset item. Dotted paths work, for example images.0.url.

## `layout` (type: `string`):

Tells Tesseract how the text is laid out. Try Sparse for receipts and photos with scattered text.

## `grayscale` (type: `boolean`):

Usually improves results on colored backgrounds.

## `upscale` (type: `string`):

Small text is read much better at a larger size. Word boxes are always given in original image pixels.

## `deskew` (type: `boolean`):

Detects a tilt of up to 10 degrees and rotates the page straight before OCR. Word boxes then refer to the straightened page.

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

Add a pages list with the text, confidence and word count of each page.

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

Add every word with its confidence (0 to 100) and bounding box (left, top, width, height) to each page. Makes rows much larger.

## `pages` (type: `string`):

Optional page selection, for example 1-3, 7, 10- (10 to the end). Empty: all pages up to the limit below.

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

Pages after this are not read and not charged.

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

300 suits most scans. Use 400 for tiny print. Higher is slower.

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

Only for password protected PDFs.

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

Larger files are skipped and not charged.

## `onlyNew` (type: `boolean`):

Skip URLs this Actor already read in an earlier run with the same monitor name. Useful for scheduled runs over a growing list.

## `stateName` (type: `string`):

Name of the key-value store that remembers read URLs when Only new files is on. Use a different name per list.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/8/8e/A_text-book_of_agricultural_entomology_BHL9798241.jpg"
  ],
  "languages": [
    "eng"
  ],
  "urlField": "url",
  "layout": "auto",
  "grayscale": true,
  "upscale": "auto",
  "deskew": false,
  "includePages": true,
  "includeWords": false,
  "maxPagesPerFile": 50,
  "pdfDpi": 300,
  "maxFileSizeMb": 50,
  "onlyNew": false,
  "stateName": "ctriolab-image-ocr-state"
}
```

# Actor output Schema

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

One item per file: text, confidence, pages and optional word boxes.

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

Billed pages, blank pages and failed files with reasons (not charged).

# 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 = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/8/8e/A_text-book_of_agricultural_entomology_BHL9798241.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ctriolab/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 = { "imageUrls": ["https://upload.wikimedia.org/wikipedia/commons/8/8e/A_text-book_of_agricultural_entomology_BHL9798241.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("ctriolab/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 '{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/8/8e/A_text-book_of_agricultural_entomology_BHL9798241.jpg"
  ]
}' |
apify call ctriolab/image-ocr --silent --output-dataset

```

## MCP server setup

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