# Image to Text OCR API - Extract Text from Images with AI (`tidytools/image-to-text-ocr`) Actor

Extract text from images in bulk with AI OCR: receipts, screenshots, signs, document photos and tables as Markdown, in English, Chinese, Japanese and more. Use URLs, datasets or base64. No-text and failed results are free; no start fee.

- **URL**: https://apify.com/tidytools/image-to-text-ocr.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 4 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 image transcribeds

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

**OCR image to text from receipts, screenshots, scans and photos: paste an image URL, choose plain text or Markdown, and get the extracted text in a dataset row.**

Try the prefilled public road sign (one image, no account needed):

```json
{"urls":["https://upload.wikimedia.org/wikipedia/commons/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"]}
```

Example output from this image in plain text: `{"success":true,"text":"関門大通り\nKanmon-odori","language":"ja","charged":true}`. The text is kept in its original language.

**Price:** $0.003 per image with returned text on the Free and Bronze plans ($3 per 1,000); no start fee, and images without text or with failed results are not charged. Higher plans have discounts.

For batches, put one image URL per line in **Image URLs**, pick a dataset, or send base64 through the API. Choose **Markdown (tables kept)** for tables, then export JSON, CSV or Excel. Start with a clear image; crop or enlarge small text if needed. GIF reads its first frame only; PDF and SVG are unsupported.

#### Receipt / invoice fields

Choose **Receipt / invoice fields** (`"mode":"receipt_fields"`) for an image of a receipt or invoice. The dataset row keeps the full OCR `text` and adds `receipt: {vendor, date, total, currency, review}`. For example, an image transcribed as `Vendor: ACME SUPPLIES\nInvoice Date: 2026-10-18\nTotal: USD 105.00` yields `{"vendor":"ACME SUPPLIES","date":"2026-10-18","total":105,"currency":"USD","review":false}`. Only explicit labels are used; an unlabeled shop name is not guessed, and `Subtotal`, `Tax` and `Invoice No` are not extracted as the total or vendor. Missing or ambiguous values are `null`, with `review:true` when the date, total or currency needs checking. Even `review:false` is not an accuracy guarantee: check fields against the source image before accounting use.

This mode supports **images only**. For a PDF, use [PDF, Word & Excel to Markdown Converter](https://apify.com/tidytools/document-to-markdown). A text-bearing result uses the same one `image-ocr` charge as the other modes; no-text and failed images remain free. The price and event are unchanged.

### What does Image to Text OCR do?

Give it images and get their text back:

- 🖥️ **Screenshots to text**: terminal output, app screens, web pages, chats
- 🧾 **Receipts, invoices and photos of documents**: line items and totals, in reading order
- 📦 **Bulk OCR**: a whole list of image links in one run, several at a time; every row says which image it belongs to, and one bad link never stops the rest
- 🪧 **Signs and labels** in photos
- 🌏 **Many languages and scripts**: English, German, Dutch..., Chinese, Japanese, Korean; the text stays in its original language (nothing is translated)
- 📊 **Tables**: kept as Markdown tables in *Markdown* mode, or as one line per row in *Plain text* mode

Each result has the `text`, `lineCount`, `charCount` and a `language` guess. Images come from **URLs** (Google Drive, Dropbox and OneDrive share links work), from **another Actor's dataset**, or as **base64**. Images without text can be reported as `no_text`; `no_text` results are **not charged**.

The OCR is done by an AI vision model (Llama 4 Scout) with a strict "transcribe verbatim, do not translate or summarize" instruction. The Actor downloads each image itself (so sites that block cloud servers still work), straightens it using its EXIF rotation, puts transparent images on white and scales very large images down to 2,048 px. A link to a web page instead of an image (for example a Wikimedia Commons file page) reads that page's main image.

### Use cases

- **Expense and receipt capture**: turn receipt photos into text for bookkeeping or an AI extractor
- **Screenshot search**: make screenshots and scanned notes searchable
- **E-commerce and marketplace data**: read text printed on product images and labels
- **Social media and meme archives**: text inside images for moderation or search
- **AI agents**: "What does this image say?" as a tool call

### How much does it cost?

| Event | Price |
|---|---|
| Image transcribed | **$3.00 / 1,000 images** ($0.003 per image) |

**Example:** 1,000 receipt photos with text cost 1,000 × $0.003 = **$3.00** on the Free plan; images with no text are free.

**No start fee.** Higher Apify plans get volume discounts (see the *Pricing* tab). Images with no text, images that cannot be downloaded and unsupported files are **not charged**; they carry an `errorType` (`no_text`, `not_found`, `blocked`, `too_large`, `unsupported`, `network`, `invalid_input`, `model_refused`, `ocr_unavailable`...). Every row has `charged: true` or `false`, and failed rows end with "(not charged)".

#### Control your cost

- **Before anything is read**, the run logs its plan: the number of images × the price = the most the run can cost, compared with your maximum charge per run.
- **Charged**: an image whose text was returned ($0.003). This includes a long text that was cut off (`route: "image-truncated"`, used only when the long-text route fails): the part that was read is returned and charged.
- **Free**: images without text, images that could not be downloaded or read, images that hit the time limit per image (180 seconds by default), lines that are not a URL, and the same URL given twice (processed once). A failed download says why when the server does, e.g. `image returned HTTP 400: Use thumbnail sizes listed at ...`.
- **Maximum charge per run**: set it in the run options (Console) or with `maxTotalChargeUsd` (API). When the next image could exceed it, no new image is started; the run ends with status `LIMIT_REACHED`, and the `SUMMARY` record lists the images not processed (`notProcessed`: the count and up to 100 inputs).
- **If Apify restarts the run** (server migration or Resurrect), items already finished are skipped and not charged again (`SUMMARY.resumedSkipped`).

#### Price comparison (checked September 2026)

| Actor | Price per image |
|---|---|
| **Image to Text OCR (this Actor)** | **$0.003**, no start fee, images without text free |
| zen-studio/google-lens-ocr | $0.00499 |
| zenomastro/image-ocr-text-extractor | $0.005 |
| s-r/google-lens-ocr | $0.006 |
| lergassy/ocr-text-extractor | $0.002 per file |

### How to use it

1. First try one clear image with visible text. Paste its **image URL** (or more links, one per line, for a batch); you can also upload a text/CSV file of links in the URL list below, pick a **dataset** from another run (one image URL or an array of up to 100 per item), or send **base64 images** through the API.
2. Choose the **output format**: *Plain text* (default) or *Markdown (tables kept)*.
3. Optional: a **language hint** such as "Japanese" for small or stylized script.
4. Click **Start** and export the results as JSON, CSV or Excel.

A text-free image can return `no_text`; a `no_text` result is free. If visible text was missed, try cropping or enlarging the text area and retry; **Language hint (optional)** may help. Only the first GIF frame is read, and PDF/SVG files are unsupported.

#### Input example

This is the single-image prefill; it previously produced the Japanese sign text shown above:

```json
{
    "urls": [
        "https://upload.wikimedia.org/wikipedia/commons/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"
    ]
}
```

#### Output examples (real results, September 2026)

A supermarket receipt photographed on a table (Dutch), *Markdown* mode:

```json
{
    "imageUrl": "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b3/Sahan_Supermarket_receipt%2C_Hillegersberg%2C_Rotterdam_%282021%29_02.jpg/1920px-...jpg",
    "success": true,
    "mode": "markdown",
    "text": "# SAHAN Supermarkt\nBergweg 170\n3036 BL Rotterdam\nTel: 010-265.33.99\n\n## KASSABON\n\nNummer 2-00005155\nDatum: 26/01/2021\nTime : 11.29\nCaissier Tuge\nKassa: 1\n\n| Omschrijving | Bedrag |\n|--------------|--------|\n| MIRAS OLijfoute (glas) 1L | 3,99 |\n| MERAY TRAKY A POMPOENPIT 200 | 1,99 |\n| PINAR POMPOENPITTEN 200G | 1,99 |\n| ULKER PETIBOR BISKUVI 1KG | 2,49 |\n| BROOD SIMIT * 5 x 0.75 | 3,75 |\n\nTOTAAL Incl: 14,21\nPIN Betaling 14,21\nTeruggave 0,00\nBTW Bedrag: 1,17\n\n6 Artikel / 9 Stuks\nD. uwel en tot ziens",
    "lineCount": 23,
    "charCount": 529,
    "language": "nl",
    "languageName": "Dutch",
    "imageFormat": "jpeg",
    "width": 1920,
    "height": 3413,
    "route": "image",
    "imageBytes": 2315283
}
```

All prices, totals, the date and the address are right. Mistakes on this crumpled receipt: "OLijfoute (glas)" (printed OLIJFOLIE (GLAS); the *Plain text* run read it correctly), "TRAKY A" (TRAKYA), "Tuge" (Tugce), "11.29" (11:29).

A Japanese road sign, *Plain text* mode: `"text": "関門大通り\nKanmon-odori"`, `"language": "ja"` (exact).

A Japanese terminal screenshot (24 lines, English and Japanese): every process line, the `uname` output, the Japanese date `2006年 10月 14日 土曜日 19:53:00 JST` and `こんにちはウィキペディア` were read correctly.

A landscape photo without text: `"success": false, "errorType": "no_text"`, not charged.

If the AI model declines to read an image (an answer like "I can't help with this request") the image is asked once more with another prompt; if it still declines, the row is `"success": false, "errorType": "model_refused"` and not charged. If the OCR service itself does not answer (busy or a server error), the row is `"errorType": "ocr_unavailable"` and not charged, and the image can simply be run again. A refusal is never returned as the transcription.

`route` says how the text was read: `image` (one vision call) or `page` (long or very dense text: when the first answer reaches its length limit of about 2,000 tokens, or the vision call cannot finish in time, the image is read again through our page-OCR route, which allows answers of up to about 4,096 tokens). A `SUMMARY` record in the key-value store has the run `status` (`SUCCESS`, `PARTIAL_RESULTS`, `FAILED`, `NO_RESULTS`, `LIMIT_REACHED`) and counts of transcribed, no-text and failed images. An image without text is a valid answer, not a failure: a run where every image was either transcribed or had no text ends as `SUCCESS`. Every row has `index` (the input order: URLs first, then base64 images) and `input`; rows from a dataset also have `datasetItemIndex`, and base64 images sent as `{"id": ..., "data": ...}` return their `id`.

### Accuracy (our test, September 2026)

We checked the output against the images by eye:

| Image | Result |
|---|---|
| Receipt photo (Dutch, crumpled) | All numbers, totals, date and address right; 2-4 misread words per run |
| Japanese road sign | Exact |
| Japanese + English terminal screenshot | Correct, including the Japanese lines |
| Wikipedia history table (screenshot, 7 rows × 13 columns) | All dates, sizes, size changes and comments right; some cells shifted by one column and the user names (hidden in the image) left empty |
| English terminal `ls -l` screenshot (long text) | All file names, sizes and times right, but written as a Markdown table, and single-digit days ("Jun  1") were dropped |
| Hong Kong restaurant sign photo (stylized Chinese, at an angle) | About 60% of the dishes read correctly; the phone number right; the large calligraphic sign and some characters wrong |
| Poem photographed upside down, 90° turned, tilted 5°, blurred, as WebP, with transparency (test of October 2026) | Exact in every case; the upside-down one is read again turned upright (`turnedUpright`) |
| Same poem shrunk to 300 px wide (JPEG) / 150 px wide | Exact / 4-6 misread words |
| Swiss restaurant receipt (German), TIFF page of English text, Hindi page, Chinese poem (5 lines) | All right (the receipt: names, items, totals, VAT) |
| Landscape photo, blank image, 1×1 pixel image | Correctly reported as no text, not charged |

In short: printed text, screenshots and receipts work well; stylized, handwritten, angled or very small text is less reliable, and the AI can misread or occasionally drop characters. Check important values (amounts, IDs) before you rely on them.

### Use with AI agents (MCP)

Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/image-to-text-ocr) to Claude, Cursor or any MCP client, then ask e.g. "Read the text in these three receipt photos and give me the totals."

```json
{ "urls": ["https://example.com/receipt.jpg"], "mode": "text" }
```

Failed images and images without text are not charged and carry an `errorType`.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~image-to-text-ocr/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://upload.wikimedia.org/wikipedia/commons/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"],"mode":"text"}'
```

Base64 input: `{"imagesBase64": ["data:image/png;base64,iVBORw0..."]}`, or with your own ids: `{"imagesBase64": [{"id": "receipt-17", "data": "iVBORw0..."}]}`. The URL-list field `imageUrls` (`[{"url": ...}]`) is also accepted, but Apify rejects the whole run (HTTP 400) when it holds a blank line or a bare domain; `urls` (plain strings) never fails the run.

**Schedules and integrations:** run it daily or weekly with Apify Schedules, get a webhook when a run finishes, or send the results to Zapier, Make, n8n, Google Sheets, Slack and other apps with Apify integrations. Results can be exported as JSON, CSV, Excel or XML.

### Advanced settings

- **Proxy**: optional Apify Proxy for downloading images from sites that block data-center requests (billed to your Apify account). Without a proxy, a refused download (HTTP 401/403/429) is retried with an honest bot user agent, then with a referrer from the image's own site (hotlink protection).
- **Parallel images**: how many images are processed at the same time (1-10).
- **Time limit per image**: 180 seconds by default (30-300); a dense page of text can need 1-3 minutes. An image that takes longer (a stuck download or a very long text) fails with `errorType: "timeout"` and is not charged, so one slow image cannot hold the run.

### Limitations

- Formats: JPEG, PNG, WebP, GIF (first frame), AVIF, HEIC/HEIF (iPhone photos) and TIFF, up to 20 MB and 100 megapixels. SVG and PDF are not supported: a PDF fails with `errorType: "unsupported"` and a pointer to our PDF, Word & Excel to Markdown Converter, which has OCR for scanned PDFs.
- Very large images are scaled down to 2,048 px on the long side, so tiny print in huge images can be lost; crop them first.
- Very long text (more than about 2,000 tokens, e.g. a full restaurant menu) goes through the page route, which answers in Markdown and tends to turn column-aligned text into tables; in *Plain text* mode those tables become tab-separated lines. Dot leaders between a dish and its price ("Soup ........ 2,50") are written as " ... ".
- Photos turned 90° without an EXIF rotation tag are read as they are. Photos that are **upside down** are detected by a quick orientation check (it runs alongside the first read, so it adds no waiting time) and read again turned upright; such rows carry `turnedUpright: true`. This costs you nothing extra. Very small or blurry text (about 150 px wide for a whole page) still gets misread words; use a larger version when you can.
- The `language` value is a guess from the script and common words; for Latin-script text it tells apart English, German, Dutch, French, Spanish, Italian, Portuguese, Polish, Swedish and Vietnamese (`und` when unclear).
- AI OCR can misread characters, and on rare occasions skip a line; it does not return per-word positions or confidence scores.

### FAQ

**Why did I get `no_text`?** The model found no readable text. Crop to the printed area, use a larger image, or add a language hint for small or stylized text. A `no_text` row is free.

**Why did an image time out?** A slow download or dense page can exceed **Time limit per image** (180 seconds by default). Try a smaller crop or raise the limit (up to 300 seconds). A `timeout` row is free.

**Why was my input invalid?** Use a full public HTTPS image URL in **Image URLs** (`urls`), one per line; remove blank lines and private addresses. Invalid base64 also gives `invalid_input`. PDF and SVG are unsupported. The `imageUrls` file/list editor can reject a whole run if it contains a blank line or bare domain; `urls` reports bad lines as free rows.

**Why could the image not be downloaded?** `not_found` means the link is missing; `blocked` means the host refused the request. Try a public direct image link or an image you have permission to use, and follow the source site's robots.txt. The Actor does not log in or bypass a login wall. Optional Apify Proxy can help with permitted public sources and is billed by Apify.

**When am I charged?** Only a row with returned image text is charged: $0.003 on Free and Bronze plans, including a partial transcription marked `image-truncated`. `no_text`, invalid inputs, failed downloads, unsupported images, model failures and timeouts are not charged; duplicates run once. Check each row's `charged` field.

### Support

Open an issue in the **Issues** tab with the image URL. Issues are checked regularly.

Found this useful? A short review on the Store helps other users find it.

# Actor input Schema

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

First run: try one clear image with visible text. Text-free images can return no_text; no_text results are free. If text was missed, crop or enlarge it, then retry (Language hint may help). One direct image URL per line (JPEG, PNG, WebP, GIF first frame, AVIF, HEIC, TIFF; max 20 MB). PDF/SVG unsupported. Google Drive, Dropbox and OneDrive share links are accepted. Or use imageUrls, datasetId or imagesBase64. Invalid lines are free; duplicate URLs run once.

## `imageUrls` (type: `array`):

The same as "Image URLs", in the URL list editor: you can also upload or link a text/CSV file of image links.

## `mode` (type: `string`):

Plain text keeps line breaks; Markdown keeps tables. Receipt / invoice fields keeps the full transcribed text and adds receipt.vendor/date/total/currency/review from explicit labels only; missing or ambiguous values need review. Images only; for PDF use PDF, Word & Excel to Markdown Converter. Fields are not accounting-grade and must be checked against the image. Same image-ocr charge for returned text; no-text and failed images are free.

## `language` (type: `string`):

Main language of the text, e.g. Japanese, Traditional Chinese, German. Optional: the text is transcribed in its original language either way; a hint can help with small or stylized script.

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

Take image URLs from another Actor run's dataset (e.g. a scraper's image field).

## `datasetField` (type: `string`):

Field with the image URL, e.g. "imageUrl", or an array of image URLs such as "images" (up to 100 per item). Empty = the first URL field found.

## `imagesBase64` (type: `array`):

Images sent as base64 strings, data URLs (data:image/png;base64,...) or objects {"id": "receipt-17", "data": "<base64>"} (the id is returned with the result), for images that are not online. Mainly for API and AI-agent use.

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

How many images are transcribed at the same time.

## `proxyConfiguration` (type: `object`):

Only used to download images from sites that block data-center requests. Apify proxy usage is billed to your Apify account.

## `maxSecondsPerImage` (type: `integer`):

An image that is not finished in this time (slow download or a very long text) is reported as failed with errorType "timeout" and is not charged, so one stuck image cannot hold the run. A dense page of text can need 1-3 minutes.

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"
  ],
  "mode": "text",
  "maxConcurrency": 4,
  "maxSecondsPerImage": 180
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `full` (type: `string`):

No description

## `details` (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/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/image-to-text-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/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("tidytools/image-to-text-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/6/67/Japanese_Road_Sign_119_%28Street_name%29.jpg"
  ]
}' |
apify call tidytools/image-to-text-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/image-to-text-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/DuFFQzZueyf7cBjft/builds/Vd4lWpaBmfzzMMB9U/openapi.json
