Image OCR – Extract Text from Images, Screenshots & Scans avatar

Image OCR – Extract Text from Images, Screenshots & Scans

Pricing

$3.00 / 1,000 image with texts

Go to Apify Store
Image OCR – Extract Text from Images, Screenshots & Scans

Image OCR – Extract Text from Images, Screenshots & Scans

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.

Pricing

$3.00 / 1,000 image with texts

Rating

0.0

(0)

Developer

Francesco Antonio Russo

Francesco Antonio Russo

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

2 days ago

Last modified

Categories

Share

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 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):

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:

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:

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

FieldDefaultWhat it does
urlsprefilled samplesDirect links to the images, up to 10,000 here. For more, use datasetId.
datasetIdnoneA dataset of your account with the image links.
urlFieldurlThe field with the link in each dataset row; a path such as image.src works.
keepFieldsnoneDataset fields copied to source in each result (up to 20).
keyValueStoreIdnoneA key-value store of your account whose records are images.
keyPrefixnoneRead only the keys that start with this text.
languages["eng"]One to four language codes read together (list below).
outputLeveltexttext, lines (lines with boxes) or words (words with boxes too).
minWordConfidence0Leave out words the engine is less sure about than this (0-100).
layoutautoauto, column, block, line or sparse.
grayscaletrueRemove colour before reading.
enhanceContrastfalseStretch the brightness range (faded scans, poor light).
upscaleautoauto (second try enlarged when the first reading is unsure), off, 2, 3.
straightenfalseCorrect a slightly tilted page before reading.
rotate0Turn every image by 90, 180 or 270 degrees first.
dedupetrueRead an image once however many times it appears.
maxImagesnoneStop after this many input entries.
maxFileMegabytes15Largest file (1-50 MB); bigger is free and not read.
respectRobotsTxttrueCheck robots.txt of every site and redirect.
requestsPerSecond2Per site (1-5).
maxConcurrency3Images read at the same time (1-8).
timeoutSecs30Download 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):

{
"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

FieldTypeDescription
inputIndexintegerPosition 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.
urlstring or nullThe 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.
statusstring"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).
textstring or nullAll the text found, in reading order, one line per line of the image and a blank line between paragraphs. Null unless status is "ok".
confidencenumber or nullMean 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.
languagestring or nullThe OCR language(s) used, joined with "+" (for example "eng+ita"). Null when the image was not read.
wordCountinteger or nullNumber of words in the text (after "minWordConfidence", if you set it). 0 for "no_text". Null when the image was not read.
lineCountinteger or nullNumber of text lines found. 0 for "no_text". Null when the image was not read.
linesarray or nullOnly 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".
widthinteger or nullWidth in pixels of the image as displayed (EXIF orientation and your "rotate" applied). Null when the file could not be decoded.
heightinteger or nullHeight in pixels of the image as displayed. Null when the file could not be decoded.
formatstring or nullWhat 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.
sizeBytesinteger or nullSize of the downloaded file in bytes. Null when nothing was downloaded.
reasonstring or nullWhy a row is not "ok": a plain sentence (what happened and what to do). Null for "ok".
finalUrlstring or nullThe address the image was finally downloaded from, after redirects. Null when nothing was fetched.
duplicateOfinteger or nullFor status "duplicate": the inputIndex of the first row with the same address or the same image. Null otherwise.
sha256string or nullSHA-256 of the downloaded bytes: the same image has the same value whatever its address. Null when nothing was downloaded.
preprocessingobject or nullWhat 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.
sourceobject or nullThe fields of your dataset row that you listed in "keepFields", copied as they were. Null otherwise.
processingMsintegerMilliseconds spent on this image (download, preparation and reading).
processedAtstringISO 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 textCost
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.