Image to Text OCR: Markdown, Word Boxes & RAG Chunks avatar

Image to Text OCR: Markdown, Word Boxes & RAG Chunks

Pricing

from $7.00 / 1,000 image processeds

Go to Apify Store
Image to Text OCR: Markdown, Word Boxes & RAG Chunks

Image to Text OCR: Markdown, Word Boxes & RAG Chunks

Extract text from PNG, JPG, TIFF and WebP images offline with Tesseract. Markdown, plain text, word boxes, confidence scores and RAG chunks. No API key.

Pricing

from $7.00 / 1,000 image processeds

Rating

0.0

(0)

Developer

Bongo Seakhoa

Bongo Seakhoa

Maintained by Community

Actor stats

1

Bookmarked

3

Total users

2

Monthly active users

22 days ago

Last modified

Categories

Share

Extract the text from PNG, JPEG, TIFF, WebP, BMP and GIF images with Tesseract OCR, offline inside the Actor. Each image gives you plain text, Markdown, a confidence score and RAG-ready chunks, and optionally word and line boxes with pixel coordinates. No API key and no AI service: built for scans, screenshots and clean document exports that you want to search, index or feed to a retrieval pipeline.

What it outputs

  • Text and Markdown per image. Plain text keeps the lines as Tesseract read them. markdown rebuilds paragraphs and joins words hyphenated at line ends; it also marks headings (from text size) and bullet and numbered lists where it can tell them apart. That structure is best-effort, because OCR gives no document structure: a heading can come out as plain text, or a large line as a heading. The full Markdown is also saved as a .md file.
  • Confidence you can filter on. Every image row has mean_confidence (0-100) and word_count; an image whose mean word confidence is below 60 is marked partial with a LOW_CONFIDENCE warning.
  • Word and line boxes (optional). JSON files with the pixel box, confidence and text of every word or line, plus Tesseract's block, paragraph and line numbers.
  • RAG chunks. The Markdown split at headings, paragraphs and sentences, with heading_path, character offsets and a stable chunk_id, in the same chunk format as the PDF to Markdown & RAG Chunks Actor.
  • Rotation fixed. Scanned pages turned sideways or upside down are detected and turned upright before OCR (rotation_deg says by how much). Phone photos are turned upright from their EXIF orientation. When detection is unsure and the text reads poorly, OCR is tried again with the image turned 90 and 270 degrees and the best reading is kept.
  • Multi-page TIFFs. Each page is its own row and its own charge, up to maxFramesPerFile.
  • Honest failures. Broken, unsupported, oversized or unreachable files get a status row with an error code and are never charged.
  • No AI service. Everything runs inside the Actor's container: Tesseract OCR 5 with its own trained language data, and Pillow for reading the images. Nothing is sent to a language model or a third-party API.

How to use it

  1. Paste one or more image URLs (direct links to the files).
  2. Or pick a key-value store that holds your files (upload them in Apify Console under Storage), optionally naming the record keys to use. Without keys, the run reads the records the store holds when the run starts; records added later are not included.
  3. Leave the defaults or adjust the options, then start the run.
  4. Open the Output tab. Overview lists every image with its status, confidence and charges; Images shows the text and Markdown; Chunks lists the RAG chunks. Markdown and box files are in the run's key-value store.

Starting the Actor with no input converts a small bundled synthetic sample so you can see the output format first. The sample is charged none of this Actor's events; Apify's standard Actor start fee still applies, as it does to every run.

Input

{
"sources": [
"https://example.com/scans/invoice-0042.png",
{ "url": "https://example.com/scans/contract.tiff" }
],
"ocrLanguages": ["eng", "deu"],
"layout": "auto",
"outputs": ["text", "markdown", "chunks"],
"chunkSize": 1500
}
FieldDefaultWhat it does
sourcesnoneImage URLs (PNG, JPEG, TIFF, WebP, BMP, GIF), as strings or {"url": ...} objects. At most 1,000 per run.
keyValueStoreIdnoneA key-value store holding images. Without keyValueStoreKeys, every record whose key has an image file extension (.png, .jpg, .tiff ...) or no extension at all (like scan-0001) is read, up to 1,000; the format comes from each file's first bytes. Records named like other files (.json, .txt ...) are left out and counted under ignored_keys in the SUMMARY record. A record that turns out not to be an image gets a free UNSUPPORTED_FORMAT row.
keyValueStoreKeys[]Record keys to read from that store.
ocrLanguages["eng"]1 to 3 of eng, deu, fra, spa, ita, por, nld, hun.
layoutautoauto (pages, documents), single_block (one block of text) or sparse (screenshots, forms, signs).
autoRotatetrueDetect pages turned by 90, 180 or 270 degrees and turn them upright first. Skipped below about 300 px of height.
modeconvertestimate counts the images and TIFF pages that would be read and the projected price, without OCR and without event charges.
outputs["text", "markdown", "chunks"]Any of text, markdown, words (word boxes file), lines (line boxes file), chunks.
chunkSize1500Maximum characters per chunk (200-8,000).
chunkOverlap150Characters repeated between consecutive chunks (0-1,000, at most half of chunkSize).
maxPixelsMp12Images above this many megapixels are scaled down before OCR (2-20).
maxFileSizeMb50Larger files are not downloaded (1-200; also capped at a quarter of the run's memory).
maxFramesPerFile50TIFF pages read per file (1-200); later pages are left out and not charged.
perImageTimeoutSecs120Time limit per image or TIFF page (10-600 s); a slower one fails with TIMEOUT and is not charged.

Output

The dataset holds several kinds of rows, told apart by record_type. Filter on it when you read the dataset through the API. The examples below are rows of the bundled sample, trimmed where marked with ....

An image row (record_type: "image"), one per image and one per TIFF page. This is the bundled sample's row from a run of the Actor image in CI on 2026-10-02:

{
"record_type": "image",
"document_id": "2ba212a710c13dbf",
"document_index": 0,
"source": "bundled-sample:sample.png",
"source_type": "sample",
"status": "ok",
"error_code": null,
"error_message": null,
"file_name": "sample.png",
"file_size_bytes": 35438,
"format": "png",
"frame_index": 0,
"frame_count": 1,
"width": 1200,
"height": 900,
"rotation_deg": 0,
"languages": ["eng"],
"layout": "auto",
"mean_confidence": 95.6,
"word_count": 87,
"text": "Warehouse Inspection Summary\n\nThis page is a synthetic sample made for the Image to Text OCR\nActor. It holds a heading, ...\n\nReference: SYN-2026-0042\nSite: North Storage Hall\nInspected on: 2 October 2026\nResult: Passed",
"markdown": "# Warehouse Inspection Summary\n\nThis page is a synthetic sample made for the Image to Text OCR Actor. It holds a heading, ...\n\nReference: SYN-2026-0042\n\nSite: North Storage Hall\n\nInspected on: 2 October 2026\n\nResult: Passed",
"markdown_url": "https://api.apify.com/v2/key-value-stores/.../records/img0001-2ba212a710c13dbf-f000.md",
"words_url": null,
"lines_url": null,
"chunk_count": 1,
"warnings": [],
"charged_events": {},
"elapsed_ms": 901,
...
}

charged_events is empty because the sample is free; for your images it shows {"image-processed": 1}. In a multi-page TIFF, the row of the first page that was read carries the charge for the whole file and the other pages' rows show {}. words_url and lines_url link JSON files when you select those outputs; each word has text, conf, the box x, y, w, h in pixels, and Tesseract's block, par, line and word numbers.

A chunk row (record_type: "chunk") from the same run:

{
"record_type": "chunk",
"document_id": "2ba212a710c13dbf",
"chunk_id": "b18251fcc0c9f0d3388379f2",
"chunk_index": 0,
"text": "# Warehouse Inspection Summary\n\nThis page is a synthetic sample made for the Image to Text OCR Actor. ...\n\nResult: Passed",
"char_start": 0,
"char_end": 506,
"heading_path": ["Warehouse Inspection Summary"],
"page_start": null,
"page_end": null,
"approx_tokens": 127,
"frame_index": 0,
"source": "bundled-sample:sample.png",
"document_index": 0
}

Images have no pages, so page_start and page_end are always null; frame_index says which image or TIFF page the chunk comes from.

The dataset is not in reading order: an image's chunk rows are stored before its image row, and in a multi-page TIFF the row of the first page read comes after the other pages. Sort by document_index, frame_index and chunk_index (or record_type first) when the order matters, for example before exporting to CSV or Excel.

Error codes in error_code: UNSUPPORTED_FORMAT (not one of the supported image formats; a PDF gets a link to the PDF Actor), IMAGE_TOO_LARGE (over 60 megapixels), IMAGE_TOO_SMALL (under 8 pixels on a side or 200 pixels in all, such as tracking pixels and spacers), CORRUPT_IMAGE (the file is damaged), DOWNLOAD_FAILED, FILE_TOO_LARGE, BLOCKED_DESTINATION, TIMEOUT, OUT_OF_MEMORY, WORKER_CRASHED, INTERNAL_ERROR, INVALID_INPUT, and CHARGE_LIMIT for skipped images. Warnings: NO_TEXT_FOUND, LOW_CONFIDENCE, DOWNSCALED, ROTATION_UNCERTAIN, FRAMES_TRUNCATED, FIRST_FRAME_ONLY, FRAMES_FAILED, and CHARGE_LIMIT on a TIFF whose later pages were cut off by your maximum charge.

Every chunk row has document_id, chunk_id, chunk_index, text, char_start, char_end, heading_path, page_start, page_end and approx_tokens, the same fields as the other Actors listed below, so their chunks can share one vector index. markdown[char_start:char_end] is exactly the chunk's text.

Row status is ok, partial (OCR ran but its mean word confidence is below 60, so the text may contain errors: warning LOW_CONFIDENCE), failed (nothing extracted; error_code says why) or skipped (not started: CHARGE_LIMIT when your maximum charge was reached, TIMEOUT when the run was about to time out). If Apify restarts a run while an item is being charged, that item is not processed again: its row carries a BILLING_UNCERTAIN_AFTER_RESTART warning, and its charged_events may not list every charge made before the restart. A restart just after an image's chunk rows were saved can repeat those rows with the same chunk_id, and the rows of a TIFF's other pages with the same document_id and frame_index (the image is charged once), so dedupe chunks by chunk_id and page rows by document_id and frame_index. The run's key-value store also gets a SUMMARY record with the counts for the whole run (files, and TIFF pages under frames), plus charge_limit_reached when your maximum charge cut a file short and ignored_keys when a key-value store held records that were not read.

Pricing

This Actor is priced per event: you pay for what was processed.

EventPriceWhen it is charged
image-processed$0.007Once per image, and once per TIFF page, on which OCR ran (status ok or partial), including images where OCR found no text.

Apify adds the Actor start fee to every run ($0.00005 per run at up to 1 GB of memory, once more for each extra GB). Chunks, files and dataset rows are not charged separately: this Actor does not use the apify-default-dataset-item event.

Worked examples:

  • 1,000 screenshots: 1,000 x image-processed = $7.00, plus the start fee. One run at the default 1 GB and 1-hour timeout reads about 250 screenshots, so give such a batch a longer timeout (about 4 hours) or split it into runs; see Limits.
  • A 10-page TIFF: 10 x image-processed = $0.07. The same TIFF in estimate mode: no event charges.

Never charged by this Actor: items that fail (unsupported, too large, too small, unreachable, unreadable), items skipped at your maximum charge or the run timeout, the bundled sample, estimate mode, and TIFF pages beyond maxFramesPerFile. Only Apify's start fee applies to those runs.

Your maximum charge is respected. Before each item the Actor checks what is left of your run's maximum total charge and processes only what it can charge for, keeping one unit's price unspent below your limit. An item that cannot be afforded, and every item after it, is listed as skipped with CHARGE_LIMIT and is not charged; the run normally finishes as Succeeded. Rerun the skipped items with a higher limit.

Limits: read before you buy

  • Scans, screenshots and clean exports. Tesseract reads printed text well. Camera photos taken at an angle, in poor light or of curved pages come out much worse, and handwriting is not supported.
  • An image where OCR ran but found no text is still charged one image-processed event (its row has the warning NO_TEXT_FOUND).
  • Speed: at the default 1 GB of memory, about 3-4 s for a small label, 12-15 s for a full-HD screenshot, 17-19 s for a 200 DPI letter page and 21-38 s for a 300 DPI A4 scan (measured on Apify on 2026-10-02 with synthetic images; real files vary). 2 GB is about twice as fast, so choose it for large batches. If your images are already upright, setting autoRotate to false saves another 40-50%.
  • Run timeout: a run stops at its timeout, 1 hour by default. At 1 GB that is about 200-300 images or pages; the rest are listed as skipped with TIMEOUT and are not charged. For bigger batches raise the timeout in the run options, choose 2 GB, or split the batch.
  • Languages: only the 8 listed, at most 3 per run. Mixing scripts lowers accuracy.
  • Size: images are scaled down above maxPixelsMp (12 megapixels by default, at most 20; warning DOWNSCALED) and refused above 60 megapixels (IMAGE_TOO_LARGE). Images under 8 pixels on a side or 200 pixels in all fail with IMAGE_TOO_SMALL and are not charged.
  • No table structure: with layout auto, Tesseract usually reads a table column by column, so values lose their row (all the labels first, then all the amounts). For invoices, receipts and other tables use layout single_block, which keeps each row on one line; it can miss a very large title. Lines that end in amounts keep their line breaks in markdown.
  • Headings and lists in markdown are best-effort guesses from text size and markers; check them before relying on them.
  • Photos: a photo of a page with a background or slight blur may not be turned upright when it has no EXIF orientation (for example after a messenger app re-saved it). Such images come out partial, usually with ROTATION_UNCERTAIN; send them upright or rotate them first.
  • Formats: PNG, JPEG, TIFF, WebP, BMP and the first frame of a GIF or animated WebP (warning FIRST_FRAME_ONLY). The format is read from the file's first bytes, not its name. PDFs are refused: use the PDF to Markdown & RAG Chunks Actor. HEIC, AVIF, SVG and camera RAW files are not supported.
  • Boxes and sizes: word and line boxes, width and height are in pixels of the image as OCR saw it, after rotation and downscaling (original_width and original_height give the size before downscaling).
  • No image clean-up beyond rotation and downscaling: no deskew, denoise or dewarp.
  • Links: only public http(s) addresses are fetched, and every redirect is checked the same way. Links to private, local or cloud-metadata addresses, and links carrying a user name or password, fail with BLOCKED_DESTINATION and are not charged.
  • Per run: at most 1,000 listed sources plus up to 1,000 records read from a key-value store; files up to maxFileSizeMb but never more than a quarter of the run's memory.

FAQ

Is my file sent to an AI service? No. OCR runs inside the Actor's container with Tesseract. Your images go nowhere else, and no language model is involved.

Why is an image partial? OCR ran, but the mean word confidence is below 60 (LOW_CONFIDENCE), so the text may contain errors. It is charged like an ok image. Blurred photos, busy backgrounds and the wrong ocrLanguages are the usual causes.

Can I read the output from code? Yes. Use the dataset items endpoint and filter on record_type, or download the files from the run's key-value store.

Something does not work. Open an issue on the Actor's Issues tab with the run link and, if you can share it, a file that shows the problem. Issues are read on a best-effort basis; there is no guaranteed response time.

Why was I charged for an image with no text? OCR ran on it: the work was done, and the row says NO_TEXT_FOUND. If you are unsure whether a batch has text, try a few images first. Images that cannot be read at all (unsupported, too large, damaged) are never charged.

Can I send PDFs? No. Scanned or digital PDFs go to the PDF to Markdown & RAG Chunks Actor, which runs the same OCR on scanned pages and reads the text layer of digital ones.

What does estimate mode cost? None of this Actor's events, only Apify's start fee. It opens each file's header, counts the images and TIFF pages that would be read and shows projected_cost_usd per row. Because it reads only headers, a file damaged further in can be projected and then fail in convert mode with CORRUPT_IMAGE; failed images are never charged.

How do I find the text of one TIFF page? Each page has its own image row with frame_index (from 0); chunk rows carry the same frame_index.

Privacy and data retention

The Actor reads only the URLs and key-value store records you give it. Your files are processed inside your run and are not sent anywhere else. The log contains counts, status and error codes, never file content or URLs. The Actor keeps nothing after the run ends: the outputs live in your run's dataset and key-value store under your Apify account, and Apify's storage retention applies to them.

More Actors from the same developer

These Actors share one output format for chunks, so their results can go into the same index:

About the sample and the tests

The bundled sample and every test file used to develop this Actor are synthetic: generated for the purpose and labelled synthetic (see sample/PROVENANCE.md in the source). No third-party files were used.

Licences

The Actor is proprietary software by Bongo Seakhoa. It is built on open-source components under permissive licences (Tesseract OCR and its language data (Apache-2.0), Pillow (MIT-CMU), pytesseract (Apache-2.0), the Apify SDK (Apache-2.0) and httpx (BSD-3-Clause), among others). certifi (MPL-2.0) is included unmodified. Tesseract comes unmodified from Debian packages; the Debian base image also contains GPL and LGPL system programs and libraries, used unmodified as separate programs and libraries. The full list and licence texts are in the THIRD_PARTY_NOTICES file shipped with the Actor image.