sparse segmentation mode for screenshots, and read the text and confidence per image. It costs $3 per 1,000 images.Copying text out of one screenshot is easy. Doing it for a few thousand support screenshots, receipt photos or saved social posts is not, and the results vary a lot depending on whether the image is a clean UI capture or a skewed phone photo.
Generic OCR settings are tuned for full pages of text, so they often miss the scattered labels in a screenshot or merge columns in a receipt. The Image to Text OCR actor runs Tesseract 5 with a choice of page segmentation modes, returns a confidence score per image, and can return every word with its pixel box.
This guide picks settings for different image types, filters out unreliable results in Python, and uses word boxes to find and redact sensitive text.
Step by step
Get public links to the images
The actor reads images by URL: PNG, JPEG, TIFF, WebP, GIF, BMP and JPEG 2000. HEIC and AVIF photos from phones and SVG are not supported, so convert photos to JPEG first. Files can be up to 100 MB each.
Pick a segmentation mode for the image type
pageSegmentationcontrols how Tesseract looks for text.autosuits photos of documents and scanned pages.sparselooks for text anywhere and is the best start for UI screenshots, forms and infographics.single-blocksuits receipts and short snippets, andsingle-columnsuits uniform text. If you are unsure, run a small sample of images in two modes and compare confidence scores.{ "urls": [ "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png" ], "languages": ["eng"], "pageSegmentation": "sparse", "minWordConfidence": 50 }Set languages and noise filtering
Choose up to 4 of the 29 languages in
languages; screenshots from a localised app may need two.minWordConfidencedrops words Tesseract is unsure about, which helps with compression artefacts, icons mistaken for letters and background patterns. Start around 40-60 and lower it if real words go missing.Keep reliable results and queue the rest
Each image is one row with
text,wordCountandconfidence. Very low confidence or very few words usually means a blurry photo, handwriting or an image with no real text. Split results into accepted and needs-review lists.import os import requests url = "https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items" payload = { "urls": [ "https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png", "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif", ], "pageSegmentation": "sparse", } resp = requests.post(url, params={"token": os.environ["APIFY_TOKEN"]}, json=payload, timeout=300) resp.raise_for_status() accepted, review = [], [] for row in resp.json(): conf = row.get("confidence") or 0 if row.get("error") or conf < 75 or (row.get("wordCount") or 0) < 3: review.append(row) else: accepted.append(row) print(len(accepted), "accepted;", len(review), "need review")Find and redact sensitive text with word boxes
Turn on
includeWordsto get every recognised word with its confidence and pixel box (left, top, width, height). The exact field names inside each word entry can be checked in your first run's dataset; the snippet below reads them defensively. With Pillow you can then draw filled rectangles over matching words, for example email addresses, before sharing the screenshot.import io, re import requests from PIL import Image, ImageDraw EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+") def redact(row, out_path): img = Image.open(io.BytesIO(requests.get(row["url"], timeout=60).content)).convert("RGB") draw = ImageDraw.Draw(img) for w in row.get("words") or []: text = w.get("text", "") if EMAIL.search(text): box = w.get("bbox") or w l, t = box["left"], box["top"] draw.rectangle([l, t, l + box["width"], t + box["height"]], fill="black") img.save(out_path)Mix in scanned PDFs if you need to
Scanned PDFs work in this actor too, page by page. If your batches regularly mix PDFs and images, OCR to Text uses the same engine and pricing and is set up for mixed document intake.
Sample input
{
"urls": [
"https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
"https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
],
"languages": [
"eng"
],
"pageSegmentation": "sparse",
"includeWords": true,
"minWordConfidence": 50
}Sample output
Each image becomes one row with its file type, word count, confidence and the extracted text, as in the sample below.
| url | fileType | pagesOcr | confidence | wordCount | text |
|---|---|---|---|---|---|
| https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png | png | 1 | 89.7 | 66 | The (quick) [brown] {fox} jumps!\nOver the $43,456.78 … #90 dog\n& duck/goose, as 12.5% of E-mail… |
| https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif | tiff | 1 | 96.3 | 60 | This is a lot of 12 point text to test the\nocr code and see if it works on all types\nof file format. |
Run it from code
cURL
curl -X POST "https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png", "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"], "languages": ["eng"], "pageSegmentation": "sparse", "includeWords": true, "minWordConfidence": 50}'JavaScript (Node.js)
// Node.js 18+ (built-in fetch). Set APIFY_TOKEN in your environment.
const input = {
"urls": [
"https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
"https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
],
"languages": [
"eng"
],
"pageSegmentation": "sparse",
"includeWords": true,
"minWordConfidence": 50
};
const res = await fetch(
"https://api.apify.com/v2/acts/actor-forge~image-to-text-ocr/run-sync-get-dataset-items",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.APIFY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
}
);
const items = await res.json();
console.log(items.length, items[0]);Python
# pip install apify-client (set APIFY_TOKEN in your environment)
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"urls": [
"https://github.com/tesseract-ocr/tessdoc/raw/main/images/eurotext.png",
"https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
],
"languages": [
"eng"
],
"pageSegmentation": "sparse",
"includeWords": True,
"minWordConfidence": 50
}
run = client.actor("actor-forge/image-to-text-ocr").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)The run-sync-get-dataset-items endpoint waits for the run (up to 5 minutes) and returns the dataset rows directly. For longer runs, start the run asynchronously and read the dataset when it finishes, as the Python example does. Replace APIFY_TOKEN with your own token from the Apify console.
What it costs
Extracting text from 2,500 support screenshots costs 2,500 x $0.003 = $7.50, with no compute charge on top. Images that fail to download or turn out blank are not charged.
| Charged for | When | Price | Per 1,000 | Worked example |
|---|---|---|---|---|
| Page ocr | Charged per image or scanned PDF page read by OCR | $0.003 | $3 | A 10-page scanned PDF = $0.03; 1,000 receipts = $3.00 |
| Page text | Charged per PDF page that already has a text layer (read directly, no OCR) | $0.0003 | $0.30 | A 100-page digital PDF = $0.03 |
| Actor Start (platform start fee) | Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). | $0.00005 | — | One run at 4 GB = $0.0002 |
Ready to try it? Image to Text OCR — Extract Text from Photos & Screenshots runs on Apify, pay per result.
Run on Apify → Read the full actor pageTips and pitfalls
- Low-resolution thumbnails OCR badly; link to the full-size image whenever you can.
- Images rotated by 90 or 180 degrees are not auto-rotated; slightly skewed photos are fine.
- Handwriting and decorative fonts come out poorly. Use the confidence score rather than assuming every result is usable.
- Charts and pictures with no text return little or nothing; that is expected, not an error.
FAQ
What is the best OCR setting for screenshots?
Start with pageSegmentation set to sparse, which looks for text anywhere on the image rather than assuming a page layout. Compare it with auto on a few samples and keep the one with higher confidence.
Can I extract text from images on a web page?
Yes, if you have the direct image URLs. Collect the image links first, then pass them in urls. Links that return a web page instead of an image are reported and not charged.
Does OCR keep the layout of the screenshot?
It keeps line and paragraph breaks in reading order, not the visual layout. For positions, turn on includeWords to get each word's pixel box.
How do I process thousands of images?
Pass them in batches via urls; maxFiles defaults to 1,000 per run and can go up to 10,000. Set a maximum total charge so a large batch stops cleanly at your budget.
Actors used in this guide
Image to Text OCR — Extract Text from Photos & Screenshots
Extract text from images (PNG, JPEG, TIFF, WebP, GIF, BMP) with Tesseract 5 OCR in 29 languages: screenshots, photos of documents, receipts, scans…
$3 per 1,000 · page ocrOCR — Scanned PDF & Image to Text (Tesseract, 29 Languages)
Extract text from scanned PDFs and images (PNG, JPEG, TIFF, WebP, GIF, BMP) with Tesseract 5 OCR in 29 languages. PDF pages that already have text…
$3 per 1,000 · page ocrRelated guides
How to OCR Scanned PDFs to Text with an API
Send scanned PDF links to the PDF OCR actor, get text back one row per page with a confidence score, and use that score to flag pages for review.…
How to Convert PDF to Markdown for RAG Pipelines
Send your PDF links to the Any URL to Markdown actor, read the markdown field of each result, then split it by heading into token-sized chunks…