# Image OCR – Extract Text from Images, Screenshots & Scans (`far003/image-ocr-text`) Actor

Image OCR: extract text from image URLs, screenshots and scans in 58 languages, with line and word boxes. Pay only for images where text is found.

- **URL**: https://apify.com/far003/image-ocr-text.md
- **Developed by:** [Francesco Antonio Russo](https://apify.com/far003) (community)
- **Stats:** 3 total users, 2 monthly users, 57.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 image with texts

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 OCR – Extract Text from Images, Screenshots & Scans

Give it a list of image links, get back the **text in each image**: screenshots, scanned pages, title pages, receipts, UI captures, signs. It reads PNG, JPEG, WebP, TIFF, GIF and BMP in **58 languages** (English, Italian, French, German, Spanish, Portuguese, Dutch, Russian, Arabic, Chinese, Japanese, Korean, Hindi and more), and can return **every line and word with its box and confidence**. The engine runs inside the Actor ([Tesseract](https://github.com/tesseract-ocr/tesseract) through tesseract.js): no outside service, no API key, nothing is sent to a third party.

**Why this one: $3 per 1,000 images, no start fee, free when no text is found, 58 languages bundled, and your images never leave the run** (the engine runs inside the Actor; no outside service sees them). You pay only for an image in which text was found; a broken link, a file that is not an image, a picture without writing, a duplicate or an error is free. It reads images, **not PDF files** (see below).

### What it does not do (please read)

- **It is OCR, not "vision".** It is excellent on clean, printed text (documents, scans, screenshots, book pages) and weak on **photographs of the real world**: a sign photographed at an angle, text on a curved surface, handwriting, stylised lettering, text smaller than about 10 pixels. In our own tests title pages and scans read well (English, Italian, French, Russian, Portuguese), while photographs of signs gave mixed results: some partly right, some with nothing reliable (free `no_text`). Test your type of image on a few samples first.
- **Digits and dates can be misread**, most of all on small, stylised or synthetic fonts (we saw "2026" read as "2020"). Do not trust a critical number blindly: check it, or use `outputLevel: words` and keep only words with a high `confidence` (or set `minWordConfidence`).
- It does **not** read PDF files, SVG drawings, HEIC/AVIF photos or animated GIFs beyond the first frame (a multi-page TIFF is read on its first page). Those give a free `not_image` row that says what the file is.
- It does **not** understand layout like a table or a form: you get the text in reading order, and with `outputLevel: lines` or `words` the position of each line or word, so you can rebuild a layout yourself.
- It does **not** guess the language: you pick it (up to 4 together). An image in a language you did not pick comes out wrong.
- It cannot get around a site that refuses bots. A `robots.txt` that closes the path, a `403` or a block page gives a free `blocked` row. Nothing is done to get past it.

### What you get

- **Text** of each image in reading order, with the mean **confidence** (0-100), the **language** used, the **word and line count**, the image **size** and real **format** (from the bytes, not the file name).
- **Lines and words with boxes** (`outputLevel`): `{ text, confidence, bbox: { x0, y0, x1, y1 } }` in pixels of the image as displayed, even when the image was enlarged for reading.
- **Preparation of the pixels, all optional and reported in the row**: grayscale, contrast boost, **enlarging small text** (automatic second try when the first reading is unsure), **straightening** of a slightly tilted page, a manual **rotation** (90/180/270) and the orientation stored in phone photos (EXIF).
- **Layout hints**: automatic, single column, single block, single line or sparse text (labels, signs).
- **Three kinds of input**: a list of links, a **dataset** of your account (with a link field, and `keepFields` to carry your own columns to the output), or a **key-value store** with image records (for example the screenshots another Actor saved).
- **Duplicates** (same address, or the same image at two addresses) are read once.
- **Polite**: an honest User-Agent that names the bot, `robots.txt` checked for every site and every redirect, at most 2 requests per second per site (the site's `Crawl-delay` wins), private and local addresses refused.
- **Honest limits**: a spending limit stops the run cleanly; a run restarted by the platform never reads or charges the same image twice.

### Who it is for

- Teams that index **screenshots, scans or exported images** for search, RAG or archives.
- Developers who add "text from image" to a pipeline (Make, Zapier, n8n, your own code) and want one predictable price per image.
- Researchers and archivists with **printed material in several languages**: the language data is bundled, so Cyrillic, Arabic or Chinese work like Latin.

### How to use

**Console**: paste the links in "Image URLs", choose the language, press Start, open the dataset (tabs "Text found" and "Image and processing"), export as JSON, CSV or Excel. The form starts with three real sample images (scanned English book title pages: *The Great Gatsby*, *A Few Notes on Cruelty to Animals*, *On the Origin of Species*), so you can press Start at once; they read at 88-93 % mean confidence.

**API** (one call, results back; the synchronous call waits up to 5 minutes):

```bash
curl -X POST "https://api.apify.com/v2/acts/far003~image-ocr-text/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg"], "languages": ["eng"]}'
```

**JavaScript**:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('far003/image-ocr-text').call({ urls: ['https://example.com/scan.png'], languages: ['eng'], outputLevel: 'lines' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const texts = items.filter((row) => row.status === 'ok').map((row) => row.text);
```

**Python**:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("far003/image-ocr-text").call(run_input={"urls": ["https://example.com/scan.png"], "languages": ["eng"]})
rows = client.dataset(run["defaultDatasetId"]).list_items().items
```

Works with Make, Zapier or n8n through the Apify integrations (run the Actor, then read the dataset), and with MCP clients through the Apify MCP server.

### Input

| Field | Default | What it does |
|---|---|---|
| `urls` | prefilled samples | Direct links to the images, up to 10,000 here. For more, use `datasetId`. |
| `datasetId` | none | A dataset of your account with the image links. |
| `urlField` | `url` | The field with the link in each dataset row; a path such as `image.src` works. |
| `keepFields` | none | Dataset fields copied to `source` in each result (up to 20). |
| `keyValueStoreId` | none | A key-value store of your account whose records are images. |
| `keyPrefix` | none | Read only the keys that start with this text. |
| `languages` | `["eng"]` | One to four language codes read together (list below). |
| `outputLevel` | `text` | `text`, `lines` (lines with boxes) or `words` (words with boxes too). |
| `minWordConfidence` | `0` | Leave out words the engine is less sure about than this (0-100). |
| `layout` | `auto` | `auto`, `column`, `block`, `line` or `sparse`. |
| `grayscale` | `true` | Remove colour before reading. |
| `enhanceContrast` | `false` | Stretch the brightness range (faded scans, poor light). |
| `upscale` | `auto` | `auto` (second try enlarged when the first reading is unsure), `off`, `2`, `3`. |
| `straighten` | `false` | Correct a slightly tilted page before reading. |
| `rotate` | `0` | Turn every image by 90, 180 or 270 degrees first. |
| `dedupe` | `true` | Read an image once however many times it appears. |
| `maxImages` | none | Stop after this many input entries. |
| `maxFileMegabytes` | `15` | Largest file (1-50 MB); bigger is free and not read. |
| `respectRobotsTxt` | `true` | Check `robots.txt` of every site and redirect. |
| `requestsPerSecond` | `2` | Per site (1-5). |
| `maxConcurrency` | `3` | Images read at the same time (1-8). |
| `timeoutSecs` | `30` | Download timeout. |

#### Languages

58 languages:

- **Latin**: English (`eng`), Italian (`ita`), French (`fra`), German (`deu`), Spanish (`spa`), Portuguese (`por`), Dutch (`nld`), Afrikaans (`afr`), Albanian (`sqi`), Basque (`eus`), Catalan (`cat`), Croatian (`hrv`), Czech (`ces`), Danish (`dan`), Estonian (`est`), Finnish (`fin`), Galician (`glg`), Hungarian (`hun`), Icelandic (`isl`), Indonesian (`ind`), Irish (`gle`), Latin (`lat`), Latvian (`lav`), Lithuanian (`lit`), Malay (`msa`), Norwegian (`nor`), Polish (`pol`), Romanian (`ron`), Slovak (`slk`), Slovenian (`slv`), Swedish (`swe`), Turkish (`tur`), Vietnamese (`vie`)
- **Cyrillic**: Russian (`rus`), Ukrainian (`ukr`), Bulgarian (`bul`), Belarusian (`bel`), Macedonian (`mkd`), Serbian (Cyrillic) (`srp`), Kazakh (`kaz`)
- **Greek**: Greek (`ell`)
- **Hebrew**: Hebrew (`heb`)
- **Arabic**: Arabic (`ara`), Persian (`fas`), Urdu (`urd`)
- **Armenian**: Armenian (`hye`)
- **Georgian**: Georgian (`kat`)
- **Devanagari**: Hindi (`hin`)
- **Bengali**: Bengali (`ben`)
- **Gujarati**: Gujarati (`guj`)
- **Kannada**: Kannada (`kan`)
- **Tamil**: Tamil (`tam`)
- **Telugu**: Telugu (`tel`)
- **Thai**: Thai (`tha`)
- **Han**: Chinese (Simplified) (`chi_sim`), Chinese (Traditional) (`chi_tra`)
- **Japanese**: Japanese (`jpn`)
- **Hangul**: Korean (`kor`)

The language data is the LSTM model `tessdata_best` (integer build `4.0.0_best_int`), copied into the image when the Actor is built. **Nothing is downloaded when it runs.**

### Output

One row per input entry, in the dataset; the run summary is in the key-value store under `SUMMARY`. A row looks like this (output level `text`):

```json
{
  "inputIndex": 0,
  "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
  "status": "ok",
  "text": "THE GREAT GATSBY\n\nBY\nF. SCOTT FITZGERALD\n\nThen wear the gold hat, if that will move her;\nIf you can bounce high, bounce for her too,\nTill she cry “Lover, gold-hatted, high-bouncing lover,\n\nI must have you!”\n—Tromas Parke D’INVILLIERS.\n\nNEW YORK\nCHARLES SCRIBNER’S SONS\n1925",
  "confidence": 92.5,
  "language": "eng",
  "wordCount": 46,
  "lineCount": 11,
  "lines": null,
  "width": 960,
  "height": 1522,
  "format": "jpeg",
  "sizeBytes": 171029,
  "reason": null,
  "finalUrl": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
  "duplicateOf": null,
  "sha256": "193c6fe859df7e7648fa4ce7b5a24989746fd47bff01f7389a29f3a232f20d49",
  "preprocessing": {
    "grayscale": true,
    "contrast": false,
    "rotate": 0,
    "straighten": false,
    "scale": 1,
    "passes": 1
  },
  "source": null,
  "processingMs": 1275,
  "processedAt": "2026-10-22T11:57:08.592Z"
}
```

#### Fields

| Field | Type | Description |
|---|---|---|
| `inputIndex` | integer | Position of the image in your input: the "urls" list first, then the rows of the dataset, then the records of the key-value store, counting from 0. Use it to join the result back to your list. |
| `url` | string or null | The address as you gave it (user name and password, if any, removed). For a key-value store record: the API address of the record. Null when a dataset row had no usable address. |
| `status` | string | "ok" = text found (charged). Free: "no_text" (read, nothing readable), "duplicate" (same address or same image as an earlier one), "not_image", "too_large", "unreachable", "blocked" (robots.txt, bot wall, private address), "invalid" (not a usable address) and "error" (the reader failed). |
| `text` | string or null | All the text found, in reading order, one line per line of the image and a blank line between paragraphs. Null unless status is "ok". |
| `confidence` | number or null | Mean confidence of the words in the text, 0-100 (Tesseract's own score; above 80 is usually reliable, below 60 deserves a look). For "no_text" it is the value that was too low. Null when nothing was read. |
| `language` | string or null | The OCR language(s) used, joined with "+" (for example "eng+ita"). Null when the image was not read. |
| `wordCount` | integer or null | Number of words in the text (after "minWordConfidence", if you set it). 0 for "no_text". Null when the image was not read. |
| `lineCount` | integer or null | Number of text lines found. 0 for "no_text". Null when the image was not read. |
| `lines` | array or null | Only with outputLevel "lines" or "words": one object per line with text, confidence and bbox {x0, y0, x1, y1} in pixels of the image as displayed (origin top left). With "words" every line also has "words" with the same three fields. Null with outputLevel "text". |
| `width` | integer or null | Width in pixels of the image as displayed (EXIF orientation and your "rotate" applied). Null when the file could not be decoded. |
| `height` | integer or null | Height in pixels of the image as displayed. Null when the file could not be decoded. |
| `format` | string or null | What the file really is, from its content: png, jpeg, webp, tiff, gif or bmp; for a file that is not a supported image the kind it looks like (pdf, svg, html, heic, avif). Null when unknown. |
| `sizeBytes` | integer or null | Size of the downloaded file in bytes. Null when nothing was downloaded. |
| `reason` | string or null | Why a row is not "ok": a plain sentence (what happened and what to do). Null for "ok". |
| `finalUrl` | string or null | The address the image was finally downloaded from, after redirects. Null when nothing was fetched. |
| `duplicateOf` | integer or null | For status "duplicate": the inputIndex of the first row with the same address or the same image. Null otherwise. |
| `sha256` | string or null | SHA-256 of the downloaded bytes: the same image has the same value whatever its address. Null when nothing was downloaded. |
| `preprocessing` | object or null | What was done to the pixels before reading: grayscale (true/false), contrast (true/false), rotate (degrees you asked), straighten (true/false), scale (1 = original size, 2 = enlarged twice, below 1 = reduced because the image is very large) and passes (2 when "upscale: auto" read the image again enlarged and kept the better reading). Null when the image was not read. |
| `source` | object or null | The fields of your dataset row that you listed in "keepFields", copied as they were. Null otherwise. |
| `processingMs` | integer | Milliseconds spent on this image (download, preparation and reading). |
| `processedAt` | string | ISO 8601 time (UTC) when the row was made. |

#### Statuses

`ok`, `no_text`, `duplicate`, `not_image`, `too_large`, `unreachable`, `blocked`, `invalid`, `error`

Only `ok` is charged. How a reading becomes "text found": the engine's words are counted as **reliable** when they have at least 3 letters or digits and the engine is at least 60 sure (2 characters need 85); an image is `ok` when it has a reliable word and at least a quarter of its words are reliable. A blank page, noise, a texture or a photo without writing gives short unsure words that fail this test and come back as free `no_text`, with the numbers in `reason`. A text of one character, or two with low confidence, is not counted as text.

#### Lines and words

With `outputLevel: "lines"` every row has `lines`: `[{ "text": "FIRENZE", "confidence": 96, "bbox": { "x0": 412, "y0": 877, "x1": 590, "y1": 921 } }]`. With `"words"` each line also has `words` with the same three fields. Boxes are in pixels of the image **as displayed** (EXIF orientation and your `rotate` applied), origin top left, also when the reading was done on an enlarged copy. With `straighten` the boxes refer to the straightened image.

#### The SUMMARY record

`SUMMARY` has the status of the run (`finished`, `partial`, `failed`, `invalid-input`, `refused`), why it stopped, the settings, the counts per status, how many rows were charged and how many were free, the versions of the engine, and the **peak memory** measured during the run (`engine.peakRssMb`).

### Price

**$0.003 per image with text found** (event `image`: "Image with text extracted"), **$3 per 1,000**. Free: broken or private links, files that are not images, files over the size limit, blocked sites, pictures with no readable text (`no_text`), duplicates, invalid entries and every image the engine failed on. **An image is charged whenever text is found in it, even if the engine is not very sure of some words**: the test is the "reliable words" rule in the section Statuses, not a high mean confidence. To keep low-quality readings out, set `minWordConfidence` (for example 70): words below it are dropped before the test, so an image whose words are all below it comes back as a free `no_text`, and the words you keep can be checked one by one with `outputLevel: words` (each has its own `confidence`). The row is stored first and charged after; a run stopped by your spending limit ends cleanly with the rows it already delivered, and nothing beyond the limit is read. If a run is restarted by the platform, rows already delivered are not read or charged again.

| Images with text | Cost |
|---|---|
| 100 | $0.30 |
| 1,000 | $3.00 |
| 10,000 | $30.00 |

The only event this Actor defines is `image`. Apify may show events of its own next to it (for example the start of a run) on the Pricing tab; they are Apify's, not ours.

### Privacy

The images are downloaded to the run and read in memory; they are not stored and not sent anywhere else. The logs never contain the text of an image, and an address is logged only as its host name. Your rows are in your own dataset. If an address contains a user name and password, they are removed before anything is fetched or written.

### Speed and memory

Measured on a laptop (Node 22, 3 images at a time): a clean page of about 1,000 x 1,400 pixels takes 1-3 seconds; a photo that needs the second try takes about 2-5. The Actor uses about **130 MB** of memory plus **110-150 MB per image being read**: three images at once with three languages peaked at 636 MB. The default memory is 2,048 MB, which is comfortable for the default `maxConcurrency` of 3; with 4,096 MB you can use 6-8. The actual peak of every run is in `SUMMARY.engine.peakRssMb`. Images are read at most up to 24 megapixels (larger ones are reduced and the boxes are scaled back) and files up to 100 megapixels are accepted.

### Data sources and licences

- **tesseract.js 7.0.0**: Apache-2.0. **Tesseract OCR engine** (inside tesseract.js-core): Apache-2.0.
- **Language data**: `tessdata_best`, integer-quantised build `4.0.0_best_int` from the `naptha/tessdata` repository, published as the npm packages `@tesseract.js-data/<lang>` (the npm wrapper is MIT; the repositories `tesseract-ocr/tessdata_best` and `naptha/tessdata` carry the **Apache-2.0** licence, checked on 22 October 2026).
- **sharp**: Apache-2.0, built on libvips (LGPL-3.0, prebuilt binary).
- The code of this Actor is closed; none of the above is modified.

### FAQ

**Which language should I pick?** The one of the text. Several together (for example `eng` and `ita`) are read in one pass but are slower and a little less sure; four is the most.

**Why was my photo `no_text`?** Because the engine found no reliable word. Try `layout: sparse`, `enhanceContrast`, `upscale: 2`, or a closer, straighter photo; if none helps, OCR is the wrong tool for that image. You were not charged.

**Does it read handwriting?** No. Printed text only.

**Can I read PDFs?** No: convert the pages to images first (a PDF gives a free `not_image` row).

**Is the order of the output the order of my input?** Rows are delivered as they finish; `inputIndex` gives the position, so sort or join on it.

**Two addresses with the same image?** The second is a free `duplicate` that points to the first (`duplicateOf`). Set `dedupe` to `false` to read both.

**A site answers 429 or 403.** The row says so and is free. A 429 is retried once automatically (after about 6 seconds, or after the time the site asks for, up to 30 seconds); if the second answer is also 429 the row is `unreachable`. Lower `requestsPerSecond`, or run later. The Actor does not rotate disguises or proxies.

**Is it allowed to fetch my images?** `respectRobotsTxt` is on by default. Turn it off only for files you are allowed to fetch.

### Limits

- Up to 10,000 links typed in the input; more through a dataset.
- Files up to 50 MB (default 15 MB), 100 megapixels; no animation, first page of a TIFF only.
- One image is read in at most 120 seconds; if the engine takes longer it is stopped and the row is a free `error`.
- Text of one or two characters is not counted as text. Boxes for languages without spaces (Chinese, Japanese, Thai) are per character group as the engine returns them.
- A key-value store record is read as an image only if its bytes are an image.

# Actor input Schema

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

Direct links to the images (PNG, JPEG, WebP, TIFF, GIF, BMP), one per entry, up to 10,000 here. For more, put the links in a dataset and use "Dataset ID". The image is read from the link itself, so it must be public. The three samples are scanned English title pages (public domain), so you can press Start at once.

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

Read the image links from a dataset of your own Apify account (for example the output of a scraper). Pick it from the list or paste its ID. Rows are read after the typed list.

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

Name of the field that holds the image link in each dataset row. A path with dots such as "image.src" works. Rows where it is missing or not a link give a free "invalid" row.

## `keepFields` (type: `array`):

Names of dataset fields to copy into each result under "source" (for example "id" or "product"), so you can join the text back to your data without counting positions. Up to 20.

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

Read the images stored as records in a key-value store of your own account (for example the screenshots another Actor saved). Every record that is an image is read; the others give a free "not_image" row. Read after the dataset.

## `keyPrefix` (type: `string`):

Read only the records whose key starts with this text (for example "screenshot-"). Empty reads all (INPUT, OUTPUT and SUMMARY are always skipped).

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

The language(s) of the text in the images. Pick the one you expect; up to 4 together are read in one pass (for a document that mixes languages), but each extra language makes the reading slower and a little less sure. Images in different languages are better run separately. The reader works on the scripts of these languages, so an image in a language that is not picked comes out wrong.

## `outputLevel` (type: `string`):

"Text" gives the text of each image. "Lines" adds every line with its confidence and its box in pixels. "Words" adds the same for every word inside each line.

## `minWordConfidence` (type: `integer`):

Words the reader is less sure about than this (0-100) are left out of the text, the lines and the counts. 0 keeps everything. 60 removes most noise from a messy photo; it can also remove real words.

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

How the text is laid out. "Automatic" suits pages and most screenshots. "Single block" suits one paragraph, "single line" one line (a number plate, a caption), "sparse text" scattered labels (a diagram, a UI screenshot, a photo of signs), "single column" text in one column of different sizes.

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

Remove colour before reading. It makes the file smaller and the reading a little faster; text on a coloured background is rarely read worse.

## `enhanceContrast` (type: `boolean`):

Stretch the brightness range of the image before reading. Helps with faded scans, photos taken in poor light and dark signs with light letters; it can make an already clean image slightly worse.

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

"Automatic" reads the image as it is and, only when the reading is not sure (below 80) and the image is not already big, reads it once more enlarged twice and keeps the better of the two. "Off" never enlarges. "2x" and "3x" always enlarge (slower, and not always better: sharp text can come out worse).

## `straighten` (type: `boolean`):

Correct a page that is slightly tilted (a photographed document, a scan that is not square) before reading, up to about 15 degrees. The boxes then refer to the straightened image. It does not turn a page that is upside down or on its side: use "Rotate" for that.

## `rotate` (type: `string`):

Turn every image by 90, 180 or 270 degrees before reading, for scans that were fed sideways or upside down. The orientation stored in photos (EXIF) is always applied first.

## `dedupe` (type: `boolean`):

An address that appears twice, or two addresses that give exactly the same image, are read once; the copies come back as free "duplicate" rows that point to the first. Turn off to read and charge every copy.

## `maxImages` (type: `integer`):

Stop after this many input entries. Leave empty for no limit. The run also stops, without error, when your spending limit is reached.

## `maxFileMegabytes` (type: `integer`):

A file bigger than this is not downloaded (or not read) and costs nothing. It is checked while the file arrives, so a huge file is cut short.

## `respectRobotsTxt` (type: `boolean`):

Check the robots.txt of every site before fetching an image from it (and of every redirect target). A path that the site closes to bots gives a free "blocked" row. Turn off only for files you are allowed to fetch.

## `requestsPerSecond` (type: `integer`):

The most requests sent to one site per second. The Crawl-delay of robots.txt, if longer, wins.

## `maxConcurrency` (type: `integer`):

How many images are downloaded and read in parallel; each one has its own OCR engine of about 150 MB. With the default memory (2048 MB) 3 is safe; with 4096 MB you can use 6-8.

## `timeoutSecs` (type: `integer`):

How long to wait for a site to answer, and the longest silence allowed while a file arrives.

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png/960px-A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png"
  ],
  "urlField": "url",
  "languages": [
    "eng"
  ],
  "outputLevel": "text",
  "minWordConfidence": 0,
  "layout": "auto",
  "grayscale": true,
  "enhanceContrast": false,
  "upscale": "auto",
  "straighten": false,
  "rotate": "0",
  "dedupe": true,
  "maxFileMegabytes": 15,
  "respectRobotsTxt": true,
  "requestsPerSecond": 2,
  "maxConcurrency": 3,
  "timeoutSecs": 30
}
```

# Actor output Schema

## `images` (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 = {
    "urls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png/960px-A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/On_the_Origin_of_Species_1859_-_Titlepage.png/960px-On_the_Origin_of_Species_1859_-_Titlepage.png"
    ],
    "urlField": "url",
    "languages": [
        "eng"
    ],
    "outputLevel": "text",
    "minWordConfidence": 0,
    "layout": "auto",
    "grayscale": true,
    "enhanceContrast": false,
    "upscale": "auto",
    "straighten": false,
    "rotate": "0",
    "dedupe": true,
    "maxFileMegabytes": 15,
    "respectRobotsTxt": true,
    "requestsPerSecond": 2,
    "maxConcurrency": 3,
    "timeoutSecs": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("far003/image-ocr-text").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/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png/960px-A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/On_the_Origin_of_Species_1859_-_Titlepage.png/960px-On_the_Origin_of_Species_1859_-_Titlepage.png",
    ],
    "urlField": "url",
    "languages": ["eng"],
    "outputLevel": "text",
    "minWordConfidence": 0,
    "layout": "auto",
    "grayscale": True,
    "enhanceContrast": False,
    "upscale": "auto",
    "straighten": False,
    "rotate": "0",
    "dedupe": True,
    "maxFileMegabytes": 15,
    "respectRobotsTxt": True,
    "requestsPerSecond": 2,
    "maxConcurrency": 3,
    "timeoutSecs": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("far003/image-ocr-text").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/thumb/4/46/F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg/960px-F._Scott_Fitzgerald%2C_The_Great_Gatsby%2C_1925%2C_title_page.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png/960px-A_Few_Notes_on_Cruelty_to_Animals_%28title_page%29.png",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/On_the_Origin_of_Species_1859_-_Titlepage.png/960px-On_the_Origin_of_Species_1859_-_Titlepage.png"
  ],
  "urlField": "url",
  "languages": [
    "eng"
  ],
  "outputLevel": "text",
  "minWordConfidence": 0,
  "layout": "auto",
  "grayscale": true,
  "enhanceContrast": false,
  "upscale": "auto",
  "straighten": false,
  "rotate": "0",
  "dedupe": true,
  "maxFileMegabytes": 15,
  "respectRobotsTxt": true,
  "requestsPerSecond": 2,
  "maxConcurrency": 3,
  "timeoutSecs": 30
}' |
apify call far003/image-ocr-text --silent --output-dataset

```

## MCP server setup

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

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/4ZrxKegLBzhNf9xxu/builds/e34aXD2xN8jC4eWOb/openapi.json
