# Image to Text OCR: Extract Text from Images, Bulk OCR API (`humble-echidna/image-ocr`) Actor

Extract text from images in bulk with Tesseract OCR: image URLs (PNG, JPEG, WebP, TIFF, GIF, BMP or one-page scanned PDFs) in, text out, with a confidence score, image size and optional line and word boxes. 32 languages incl. Chinese, Japanese and Arabic. Pay per image.

- **URL**: https://apify.com/humble-echidna/image-ocr.md
- **Developed by:** [Michael Costa](https://apify.com/humble-echidna) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 2 monthly users, 95.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 images

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

### What does Image to Text OCR do?

**Image to Text OCR** extracts **text from images** in bulk. Give it links to PNG, JPEG, WebP, TIFF, GIF or BMP files,
or one-page scanned PDFs; you get the text, a confidence score and, optionally, every line and word with its
**position in pixels**.

It runs **Tesseract**, the open-source OCR engine, inside the actor, in 32 languages: nothing is sent to a
third-party OCR service. It reads only the images you list.

[Input fields](https://apify.com/humble-echidna/image-ocr/input-schema) ·
[API](https://apify.com/humble-echidna/image-ocr/api) ·
[Use it from Claude, ChatGPT or Cursor (MCP)](#can-i-use-image-to-text-ocr-from-an-ai-agent-mcp)

**Jump to:** [Fields](#what-data-does-image-to-text-ocr-return) ·
[Price](#how-much-does-it-cost-to-extract-text-from-images) · [How to use](#how-to-extract-text-from-an-image) ·
[Input](#input) · [Output](#output) ·
[Chain after another actor](#chain-it-after-another-actor) ·
[AI agents (MCP)](#can-i-use-image-to-text-ocr-from-an-ai-agent-mcp) · [Limits](#limits) ·
[FAQ](#faq)

**Try it in one click:** the input comes pre-filled with two small public-domain scans from Wikimedia Commons (a
newspaper clipping and a book page). That's 2 images, about $0.008 (2 × $0.004, plus $0.00005 for the run start).
**Then replace them with your own images.**

### What data does Image to Text OCR return?

| Field | Example | Notes |
|---|---|---|
| `text` | `-—A favorite copy set by writing teachers\n...` | Lines end with a newline; blocks are separated by a blank line. |
| `confidence` | `82.1` | Mean word confidence, 0-100. Under ~60 usually means a poor image or the wrong language. |
| `wordCount`, `lineCount` | `29`, `4` | |
| `lines` | `[{"text": ..., "confidence": ..., "left": ..., "top": ..., "width": ..., "height": ...}]` | With **Include lines with positions**; else `null`. |
| `words` | `[{"text": "favorite", "confidence": 96.4, "left": 103, "top": 13, "width": 119, "height": 24}]` | With **Include words with positions**; else `null`. |
| `width`, `height` | `690`, `150` | The image as read (upright; PDFs rendered at 300 DPI). |
| `format`, `fileSizeBytes`, `fileName` | `png`, `28520`, `The_Quick_Brown_Fox_Newspaper_Reference.png` | Read from the file itself. |
| `languages`, `pageSegmentation` | `["eng"]`, `auto` | What you asked for. |
| `truncated`, `truncatedReasons` | `false`, `[]` | E.g. an animated GIF (only the first frame is read). |
| `sourceTitle`, `sourceIndex` | `"Receipt printer paper"`, `12` | From the dataset item the link came from (see [Chain it after another actor](#chain-it-after-another-actor)); `null` for URLs you type in. |

One result per image. The full list is under [Output](#output).

### How much does it cost to extract text from images?

You pay per image that gives you text: **$4.00 per 1,000 images**, plus $0.00005 each time a run starts.

**It's cheaper on paid Apify plans:** $3.80 per 1,000 images on Starter, $3.60 on Scale and $3.50 on Business. The prices on this page are the Free-plan price, so on a paid plan you pay less than the examples show.

- **The example below:** 2 images × $0.004 = $0.008, plus the start fee.
- **A month, for example:** 2,000 scans × $0.004 = **$8.00**.
- **Caps:** **Max images per run** in the input, and **Maximum cost per run** in the run options. The run stops
  cleanly at whichever comes first; it stops downloading as soon as the limit is covered, so a capped run is also a
  fast one.

**Never charged:** images with no text, and images that fail (not found, blocked, not an image, too large, a
multi-page file, an SVG or HEIC photo).

### How to extract text from an image

1. Open Image to Text OCR and click **Try for free** (or **Start** if you're signed in).
2. Put direct links to your images in **Image URLs**, one per line.
3. Optional: pick the **Languages**, the **Layout** (e.g. scattered text for screenshots and signs), and turn on
   **Include lines with positions** or **Include words with positions** for bounding boxes.
4. Click **Start**, then open the **Output** tab and export as JSON, CSV or Excel.

### Example: two public-domain scans

The pre-filled input:

```json
{"urls": ["https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png",
          "https://upload.wikimedia.org/wikipedia/commons/2/24/Scan_of_%22Proceedings_of_the_Cambridge_Philosophical_Society_19_%281917-1920%29%22_%28English%2C_page_36%29.png"],
 "languages": ["eng"], "pageSegmentation": "auto"}
```

The first image's result (real output from a local run on 2026-09-25, OCR mistakes included):

```json
{
  "id": "c317d5a4b899024d1b737ef0",
  "fileName": "The_Quick_Brown_Fox_Newspaper_Reference.png",
  "format": "png",
  "width": 690,
  "height": 150,
  "text": "-—A favorite copy set by writing teachers\nfor their pupils is the following, bevause.:it\ncontains: every letter in the alphabet: “A\nquick brown fox: jumps ‘over the lazy dog.”",
  "confidence": 82.1,
  "wordCount": 29,
  "lineCount": 4
}
```

The book page gave 383 words at a confidence of 85.6. Exact characters can vary slightly between Tesseract versions on
noisy scans like these.

### Ready-to-run examples

Each example opens with the input already filled in. Run it as it is, or change the input first.

- **[Extract text from images with OCR](https://apify.com/humble-echidna/image-ocr/examples/extract-text-from-images)**: Get the text from image URLs (PNG, JPEG, TIFF, WebP) with Tesseract OCR, in 32 languages, with a confidence score.

### Input

| Field | What it does |
|---|---|
| **Image URLs** | Direct links to the images, one per line. The format is read from the file, so links without an extension work. |
| Or: image links from a dataset (`datasetId`) | One of your Apify datasets, e.g. another actor's results: each item's image link is read and **Image URLs** is ignored. See [Chain it after another actor](#chain-it-after-another-actor). |
| Field with the image link (`datasetUrlField`) | Only with a dataset. Empty (default): found automatically among `imageUrl`, `image`, `displayUrl`, `images`, `src` and `url`, then one level down. |
| Languages | The language(s) of the text (up to 4). Default: English. |
| Layout | Automatic (pages, documents), automatic with rotated-text detection, one block, one line, or scattered text (screenshots, signs, diagrams). |
| Include lines with positions | Adds `lines`: each line's text, confidence and box. |
| Include words with positions | Adds `words`: each word's text, confidence and box. |
| Max images per run | Cap the number of images read. |

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png"
  ],
  "languages": ["eng"],
  "pageSegmentation": "auto",
  "includeWords": true,
  "maxResults": 100
}
```

**Languages:** English, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Czech, Slovak, Swedish,
Danish, Norwegian, Finnish, Hungarian, Romanian, Turkish, Greek, Russian, Ukrainian, Bulgarian, Arabic, Hebrew,
Hindi, Bengali, Thai, Vietnamese, Indonesian, Japanese, Chinese (Simplified), Chinese (Traditional) and Korean.
Pick every language that appears in your images; each extra one makes reading slower.

### Output

One result per image. Fields that don't apply are `null`.

```json
{
  "id": "5b1c0f6e2d9a4c7e8f3a1b2c",
  "url": "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png",
  "finalUrl": "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png",
  "fileName": "The_Quick_Brown_Fox_Newspaper_Reference.png",
  "format": "png",
  "fileSizeBytes": 28520,
  "width": 690,
  "height": 150,
  "frameCount": 1,
  "renderDpi": null,
  "languages": ["eng"],
  "pageSegmentation": "auto",
  "text": ":—A favorite copy set by writing teachers\nfor their pupils is the following, bevause.it\ncontains: every letter in the alphabet: “A\nquick brown fox: jumps‘over the lazy dog.”",
  "confidence": 86.2,
  "wordCount": 28,
  "lineCount": 4,
  "lines": null,
  "words": [
    {"text": "favorite", "confidence": 96.4, "left": 103, "top": 13, "width": 119, "height": 24}
  ],
  "truncated": false,
  "truncatedReasons": [],
  "scrapedAt": "2026-09-24T18:00:00Z",
  "sourceTitle": null,
  "sourceIndex": null
}
```

- `text`: lines end with a newline; paragraphs and blocks are separated by a blank line.
- `confidence`: the mean confidence of the words, 0 to 100 (per line and per word in `lines` and `words`).
  Under ~60 usually means a blurry, tiny or stylised image, or the wrong language.
- `width` and `height` are the image as read: upright, and for PDFs the page rendered at `renderDpi` (300 DPI).
  Boxes are in those pixels.
- `id` is stable for the same URL across runs, so you can use it to deduplicate.
- `truncated` is true when not everything was read, e.g. an animated GIF (only the first frame is read).
- `sourceTitle` / `sourceIndex`: for links read from a dataset, the item's `title` (or `name`) and its position in
  the dataset (0 = the first); `null` for URLs you type in.

### Run it on a schedule, or from your own code

1. Save your input as a [task](https://docs.apify.com/platform/actors/running/tasks) (**Save as a new task**, top
   right of the actor page) and add it to a [schedule](https://docs.apify.com/platform/schedules) (Console →
   Schedules → **Create new**): for example nightly over the day's new scans, if they're
   published at links you can list. Every run reads, and charges for, every image that gives text.
2. Collect results: download the dataset as JSON, CSV or Excel; fetch the latest run's results from the
   [API](https://apify.com/humble-echidna/image-ocr/api)
   (`GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=csv`, with
   your API token); let a [webhook](https://docs.apify.com/platform/integrations/webhooks) tell your system when a
   run succeeds; or connect it to Make, Zapier or n8n through
   [Apify's integrations](https://docs.apify.com/platform/integrations).

#### Chain it after another actor

To read the text in the images another actor found, right after it finishes, chain it with an integration (any
actor, ours or not: an image downloader, a shop or Instagram scraper):

1. Open the other actor or its saved task, go to the **Integrations** tab and add **Apify actor** →
   **Image to Text OCR**, triggered when a run **succeeds**.
2. Set `"datasetId": "{{resource.defaultDatasetId}}"` in the input (Apify fills in the finished run's dataset).
   Leave `datasetUrlField` empty to find the link automatically (`imageUrl`, `image`, `displayUrl`, `images`, `src`,
   then `url`), or name the field, e.g. `"datasetUrlField": "product.mainImage"`. Add `maxResults` to cap each run.

Every successful scrape then comes back as text, with no code. **Image URLs** is ignored when a dataset is given,
so its example scans are never read or charged. For example, after [Bulk Image
Downloader](https://apify.com/humble-echidna/bulk-image-downloader) each image it found (`imageUrl`) is read, once
even when several pages show it. Each link is read and charged like one you type in; items without a link are
skipped and counted in `RUN_STATS` (`dataset.withoutWebsite`, `dataset.notAWebsite`). At most the first 20,000 items
and 10,000 distinct links of a dataset are read per run, with your own account's access, read-only. From the API
it's the same input: pass the finished run's `defaultDatasetId` as `datasetId`.

#### Can I use Image to Text OCR from an AI agent (MCP)?

Yes, through [Apify's MCP server](https://docs.apify.com/platform/integrations/mcp), from Claude, ChatGPT, Cursor or
any other MCP client. Add this to your client's MCP configuration (or let the agent find it with the server's actor
search); your client signs you in to Apify:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=humble-echidna/image-ocr"
    }
  }
}
```

To use an [Apify API token](https://console.apify.com/settings/integrations) instead of signing in, add
`"headers": {"Authorization": "Bearer <APIFY_TOKEN>"}` next to `url`.

The agent passes image links, e.g.
`{"urls": ["https://example.com/receipt.png"], "languages": ["eng"]}`, and reads `text` and `confidence` back.

### Who it's for

Anyone with images of printed text at public links who needs the text as data: scanned pages and clippings for
archives and research, screenshots and signs, one-page scanned forms. With word positions on, it also suits
redaction and pulling fields from forms.

### Why this one?

- **One flat price, no usage bill.** You pay per result at the price above and nothing else: no compute, proxy or storage charges on top, apart from the $0.00005 run-start fee, so you know what a run costs before it starts. Results you don't get aren't charged.
- **Text, lines and words that agree.** One recognition pass gives the plain text, the lines and the words, so a
  word's box always matches the text you got. Turn on **Include lines** or **Include words** for bounding boxes
  (left, top, width, height in pixels) and a confidence per line and word: good for highlighting, redaction,
  pulling fields from forms, or throwing away low-confidence words.
- **Photos come out right.** Phone photos are turned upright from their orientation tag before reading, transparent
  images are put on white (dark text on a transparent background reads as text, not as a black square), and 16-bit
  and CMYK images are handled.
- **Honest about what it couldn't read.** An image with no text, a multi-page file, a damaged file, an SVG or an
  iPhone HEIC photo is reported in the log and in the `RUN_STATS` record with the reason, and **never charged**.
  You only pay for images that gave you text.
- **Safe with untrusted files.** Each image is decoded and read in its own process with a time and memory limit,
  and anything over 50 megapixels (including "decompression bombs": small files that unpack to gigapixels) is
  refused before it's unpacked. One bad file never stops the others.
- **Polite.** It identifies itself honestly (User-Agent `HumbleEchidnaApify`), follows each site's robots.txt (read
  once per site per run), including opt-outs for AI crawlers (GPTBot, CCBot, ClaudeBot, Google-Extended and the
  like), and only requests public web addresses on the standard ports (80 and 443).

### Limits

- **Formats:** PNG, JPEG, WebP, TIFF, GIF, BMP, and PDFs with one page. Animated GIF and WebP: the first frame.
  Multi-page TIFFs and PDFs are refused (not charged): split them into one image per page. SVG and HEIC/HEIF
  (iPhone photos) aren't read: export them as PNG or JPEG.
- **Size:** at most 40 MB and 50 megapixels per image, 2 GB of downloads per run (start another run for more).
- **Time:** at most 3 minutes per image.
- **Handwriting** and heavily stylised text are read poorly: Tesseract is built for printed text.

### FAQ

#### Why does the text have mistakes or miss parts of the image?

OCR is only as good as the image. Text should be at least
\~20 pixels high: upscale small screenshots before sending them. Pick the right language(s). For screenshots,
signs and diagrams, try the **Scattered text** layout; for a single label, **One line**; for sideways scans,
**Automatic, and detect rotated text**. Use `confidence` (or the per-word confidences) to spot what to check.

#### My PDF already has text in it. Do I need OCR?

No: a PDF-to-text converter (such as our [PDF to Text Converter](https://apify.com/humble-echidna/document-to-text)) reads the text layer exactly
and faster. This actor is for scans and photos.

#### Something that used to work now fails. Why?

The run log names the image and what went wrong, and every other image in the run is unaffected. Please open an
issue with the input you used.

#### Is it legal to extract text from images with OCR?

The actor downloads only the images you list, as a logged-out visitor, and honours each site's
robots.txt, including its opt-outs for AI crawlers. Make sure you have the right to process the images you send:
the actor reads whatever text is in them, which can include personal data if your images contain it. It keeps
nothing: images are deleted as soon as they're read, and the text goes only to your own dataset. The OCR engine is
Tesseract (Apache-2.0) with its language models (Apache-2.0); images are decoded with Pillow (MIT-CMU) and PDFs
rendered with PDFium via pypdfium2 (BSD-3/Apache-2.0).

### Related actors

| Actor | Use it when |
|---|---|
| [PDF to Text Converter](https://apify.com/humble-echidna/document-to-text) | You have multi-page PDFs (scanned or not), Word or Excel. It uses the same OCR for scanned pages, and reads PDFs that already have text directly, without OCR. |
| [Bulk Image Downloader](https://apify.com/humble-echidna/bulk-image-downloader) | You want to collect the images from web pages first. |

### Feedback and support

Found a bug, or an image that reads badly? Open an issue on the **Issues** tab with the link you used.

### Versions

Current version: **1.1**. See the Changelog tab for what changed in each version.

# Changelog

This Actor's version history is a separate document: https://apify.com/humble-echidna/image-ocr/changelog.md

# Actor input Schema

## `urls` (type: `array`):

Direct links to the images, one per line: PNG, JPEG, WebP, TIFF, GIF or BMP files, or one-page PDFs (scans), on public web addresses (http/https, ports 80 and 443). The format is read from the file itself, so links without an extension work too. Links to an image viewer or a login page don't: use the link to the file. The default is two small public-domain scans on Wikimedia Commons. Ignored when datasetId is set.

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

One of your Apify datasets, picked here or given by id, e.g. Bulk Image Downloader's or a shop or Instagram scraper's results: each item's image link is read like a line of urls (urls is ignored). Empty by default. Items without a link or with a value that isn't a web address are skipped; duplicates are read once. Each result carries sourceTitle and sourceIndex from its item. At most the first 20,000 items and 10,000 links are read, with your own account's access, read-only.

## `datasetUrlField` (type: `string`):

Only with datasetId: the item field that holds the image link, e.g. `imageUrl`, or a dotted path such as `product.image`. Leave empty (the default) to find it automatically: the first of imageUrl, image, displayUrl, images, src and url that has a link in the first 100 items, then the same names one level down. If the field holds a list of links, only the first is used.

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

The language(s) of the text, as Tesseract codes, at most 4, e.g. \["eng", "deu"] for bilingual English and German labels. Default \["eng"] (English). Pick every language that appears in your images; each extra language makes recognition slower.

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

How the text is laid out. `auto` (the default): documents, pages, columns; suits most scans and photos of pages. `autoWithOrientation`: the same, and detects sideways or upside-down text. `singleBlock`: one block (a paragraph, a receipt). `singleLine`: one line (a label, a caption). `sparse`: scattered text (screenshots, signs, diagrams, forms). If a result misses text, try `sparse` or `singleBlock`.

## `includeLines` (type: `boolean`):

Default false. When true, add a `lines` list: each line's text, confidence and bounding box in pixels (left, top, width, height). The `text` field already has every line.

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

Default false. When true, add a `words` list: each word's text, confidence and bounding box in pixels. Useful for highlighting, redaction or form fields; makes each result larger.

## `maxResults` (type: `integer`):

Stop after this many images read in total, e.g. 20. Minimum 1; leave empty (the default) for no limit. The run also stops cleanly at the maximum cost per run you set in the run options, whichever comes first. Set it to keep a run small and quick.

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png"
  ],
  "languages": [
    "eng"
  ],
  "pageSegmentation": "auto",
  "includeLines": false,
  "includeWords": false
}
```

# Actor output Schema

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

No description

## `runStats` (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 = {
    "urls": [
        "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("humble-echidna/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 = { "urls": ["https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png"] }

# Run the Actor and wait for it to finish
run = client.actor("humble-echidna/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 '{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/d/d7/The_Quick_Brown_Fox_Newspaper_Reference.png"
  ]
}' |
apify call humble-echidna/image-ocr --silent --output-dataset

```

## MCP server setup

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