# Google Lens OCR API - Handwriting, Translation & Image to Text (`s-r/google-lens-ocr`) Actor

Turn image URLs into text with Google Lens OCR. Get the full text, paragraphs with lines, optional word bounding boxes and an optional translation for scans, screenshots, photos and handwriting. Built for developers, data teams and document workflows. Pay per image read.

- **URL**: https://apify.com/s-r/google-lens-ocr.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** AI, Developer tools
- **Features:** Limited permissions
- **Stats:** 248 total users, 46 monthly users, 99.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 image ocrs

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

## Google Lens OCR API — Image to Text

Google Lens OCR for image URLs: send a scan, screenshot, photo or handwritten note and get the recognized text back as JSON, with paragraph blocks, optional word bounding boxes and an optional translation. Each image that can be read becomes one structured dataset row. Recognition and optional translation use the Google Lens engine, and your original recognized text stays in the result when translation is requested.

This actor is useful when you need searchable text from document images, structured text from screenshots, or coordinates for highlighting recognized words. It accepts JPEG, PNG, WEBP, TIFF, GIF and BMP images. Recognition quality depends on the pixels in the source: clear printed text generally gives more useful results than a blurred, very small or hard-to-read handwritten image. Check the returned text against the image when exact transcription matters.

### What you can do with Google Lens OCR

- **Digitize scanned documents.** Turn page scans, letters and forms into plain text you can search, index or feed into a language model.
- **Read text from screenshots.** Pull error messages, chat logs, receipts or product labels out of screenshots without retyping them.
- **Highlight words on the image.** Use the normalized word boxes to draw overlays, build a searchable image viewer or redact specific words.
- **Translate image text.** Request a target language and receive the translation next to the original text, for example to read a foreign-language sign or document.
- **Process batches.** Send up to 200 image URLs in one run and receive the rows in input order, ready for export as JSON, CSV or Excel from the Apify dataset.
- **Automate pipelines.** Call the actor from the Apify API, a schedule, Make, Zapier or n8n, and read the dataset when the run finishes.

### Input

`image_urls` contains direct links to image files. A link to a web page that displays an image does not serve the image itself and may produce no row. Enter the URLs as a list; API callers can also send one URL per line in a string. Blank entries are removed. Up to 200 URLs are accepted per run, and URLs beyond that limit are reported as unprocessed. Repeated URLs are processed separately and create separate rows.

The following is the real prefill used for the baseline run:

```json
{"image_urls":["https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"],"ocr_language":"en"}
```

`ocr_language` defaults to `en`. It is a recognition hint, not a filter: other scripts in the image can still appear in the output. The value is echoed on the result as `ocr_language`; that field does not claim to be the detected language.

`include_word_boxes` defaults to `true`. Set it to `false` to omit the `words` array and reduce the output size. The full text, paragraph geometry and word count remain available. This option changes the returned fields, not the number of images processed or the price.

`translate_to` is optional. Set it to the requested translation language, such as the `nl` value in the actual translation example below. Leave it absent or empty for recognition only. Translation adds separate fields and does not replace the recognized source text, paragraphs or word coordinates.

### Google Lens OCR output

One row is delivered for every successfully read image, even when that image contains no recognized text. An unreadable URL creates no dataset row. The actor retains the source URL so you can connect each result to the submitted image. Rows are returned in input order, with repeated URLs retained.

`full_text` is a string joining the nonempty text blocks with line breaks. `paragraphs` is an array of objects containing `text`, `lines` and `geometry`. The `lines` field is an array of strings. Geometry is either an object or null and may include normalized `center_x`, `center_y`, `width`, `height` and `angle_deg`, with the angle measured in degrees.

When word boxes are enabled, each element of `words` contains `word`, `separator` and `geometry`. The integer `word_count` remains present when word boxes are disabled. `image_width` and `image_height` report the source dimensions in pixels. `processed_in_seconds` measures recognition for that image, while `fetched_in_seconds` measures the complete collection time.

With translation enabled, `translated_text` is a string or null if the engine did not provide a translation, and `translate_to` echoes the requested target language. Without translation, both keys are omitted. Additional common fields are `source`, `engine`, `text` and the UTC timestamp `fetched_at`; `text` matches `full_text`.

The run summary is available in both `OUTPUT` and `summary`. It includes `itemCount`, `errorCount` and `statusMessage`. The `errors` record describes failed images. Check that record when fewer rows arrive than the number of submitted URLs. Partial delivery is indicated in the summary when a run ends before every image is attempted.

With no errors, `statusMessage` remains `N image(s) read`. With errors it is `N image(s) read, M failed`, including when every URL fails. The former `all K image URL(s) could not be downloaded` wording is replaced by this count; individual causes remain in `errors`.

### Word boxes example

This excerpt comes from the real prefill run above (`phototest.tif`, 640 × 480 pixels). The row contains 65 words; the first two elements of `words` are shown, with the other fields of the row omitted:

```json
{
  "image_url": "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
  "word_count": 65,
  "image_width": 640,
  "image_height": 480,
  "words": [
    {
      "word": "This",
      "separator": " ",
      "geometry": {
        "center_x": 0.1,
        "center_y": 0.22188,
        "width": 0.10312,
        "height": 0.06458,
        "angle_deg": 0.00013
      }
    },
    {
      "word": "is",
      "separator": " ",
      "geometry": {
        "center_x": 0.18437,
        "center_y": 0.22188,
        "width": 0.0375,
        "height": 0.06458,
        "angle_deg": 0.00013
      }
    }
  ]
}
```

To get pixel coordinates, multiply `center_x` and `width` by `image_width`, and `center_y` and `height` by `image_height`. Joining every `word` with its `separator` reproduces the recognized text.

### Translation example

This input and the following row come from the recorded translation response. The displayed row is the Arabic image from the two-image run; word boxes were disabled.

```json
{
  "image_urls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/eurotext.tif",
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/arabic.tif"
  ],
  "ocr_language": "de",
  "translate_to": "nl",
  "include_word_boxes": false
}
```

```json
{
  "image_url": "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/arabic.tif",
  "full_text": "العربي",
  "paragraphs": [
    {
      "text": "العربي",
      "lines": [
        "العربي"
      ],
      "geometry": {
        "center_x": 0.73125,
        "center_y": 0.70726,
        "width": 0.20167,
        "height": 0.28,
        "angle_deg": -0.51728
      }
    }
  ],
  "word_count": 1,
  "image_width": 600,
  "image_height": 200,
  "ocr_language": "de",
  "processed_in_seconds": 1.782,
  "source": "google-lens-ocr",
  "engine": "google_lens",
  "text": "العربي",
  "fetched_at": "2026-10-01T16:17:32.802Z",
  "translated_text": "Arabisch",
  "translate_to": "nl",
  "fetched_in_seconds": 2.77
}
```

### How this Google Lens OCR actor compares

- **Versus self-hosted OCR such as Tesseract.** There is no model to install, tune or host, no GPU and no language packs to manage. You send URLs and receive structured rows. Self-hosting can be cheaper at very high volume when you already run the infrastructure.
- **Versus general cloud vision APIs.** Those require a cloud account, credentials and your own code around the API. This actor runs on your Apify account, bills per image read and writes results straight into a dataset you can export or connect to other tools.
- **Versus Google Lens reverse image search actors.** Those return visually similar images and matching web pages. This actor focuses on text: full text, paragraphs, lines, word geometry and translation, in a stable row format.
- **Versus copying text by hand from the Lens app.** The app is fine for one image. For tens or hundreds of images, or for text that must land in a spreadsheet or database, a batch run with machine-readable output saves the manual work.

### Pricing

The current pay-per-event price is **$0.006 per successfully read image**, plus the Apify **actor-start event at $0.005 per GB of actor memory, with a minimum of one event**. The normal actor configuration uses 1 GB. Image recognition is charged after the corresponding rows are delivered. A failed download creates no OCR event; a successful image with empty recognized text still creates one row and one OCR event. Optional translation and word coordinates do not add another image event.

Users without a paid Apify plan can process the first ten image URLs per run. The input is limited before processing, and only delivered rows are charged. When more URLs were supplied, the summary reports `urlsNotProcessed`. Upgrade the Apify plan or split the input across runs to process more images.

### FAQ

#### Why did I receive fewer rows than image URLs?

Some URLs may return an error, time out, serve HTML or contain an unsupported image. Those images are described in the errors record and do not create rows. The free-plan limit and the 200-image maximum can also reduce the number processed. Compare the input, row count and summary rather than assuming every link is still usable.

#### Can I recognize text without keeping every word box?

Yes. Set `include_word_boxes` to false. You still receive the full text, paragraphs, source dimensions and word count. Only the words array is omitted, so consumers that need searchable text without individual coordinates can keep a smaller result.

#### Does translation overwrite the original text?

No. The original full text and paragraphs remain in the source language. The translation is returned separately as `translated_text`. If no translation was provided, that field is null. If you did not request translation, the translation fields are absent.

#### What happens to an image with no text?

A readable image without recognized characters is a successful result with an empty full text string and zero words. It is still one processed image and one chargeable row. A URL that cannot be read is different: it produces an error and no row.

#### Does Google Lens OCR read handwriting?

The engine reads handwritten text as well as printed text, but results depend strongly on legibility, contrast and resolution. Neat handwriting in a sharp photo usually comes back well; cursive or faint writing may be partly missed or misread. Always check handwriting results against the image.

#### Which languages are supported?

Recognition is not limited to the `ocr_language` hint: the baseline translation run recognized Arabic text with the hint set to `de`. Set `ocr_language` to the main language of your images and use `translate_to` when you want a translation in another language.

#### How should I submit large images?

Use a direct image link and keep the source below 30 megapixels. Images exceeding that pixel limit or the 128 MiB download limit should be resized before submission. The source dimensions remain available on successful rows, allowing downstream consumers to interpret the normalized coordinates.

#### What happens if an image takes too long to read?

An image initially has 18 seconds for downloading and recognition. The first image on a processing page can use up to about 44 seconds. If a later image exceeds its initial limit, it gets one additional attempt as the first image on the next page, with that longer budget. A timeout on the first position produces one `upstream_timeout` error identifying its URL; the remaining images continue. These limits are lower than the former 90-second limit. Resize slow or large images, or submit them individually. Waiting for processing capacity does not count toward the image limit: pending images are retried while the run has time left, and unfinished work is marked as partial. Undelivered images create no OCR charge. Once all pages finish, the temporary page-budget warning is removed from the run summary; actual image errors remain visible.

#### Can image URLs point to a private network?

Image URLs must use HTTP or HTTPS and resolve to public IP addresses. Private-network, loopback and link-local addresses are rejected, including when a public URL redirects to them. Use a publicly accessible direct image link.

# Actor input Schema

## `image_urls` (type: `array`):

Direct URLs of images to OCR (JPG, PNG, WEBP, TIFF, GIF…). One per line, up to 200 per run; URLs past the 200th are reported as unprocessed. Each URL is downloaded here with a browser User-Agent and then sent to Google Lens, so the link must serve the image file itself, not a page that displays it. A URL that 403s, times out or turns out not to be an image is recorded in the run's errors key and produces no dataset row, so fewer rows than URLs is normal and worth checking there.

## `ocr_language` (type: `string`):

Two-letter language hint for recognition (en, nl, de, fr, ja, zh…). Lens auto-detects, this only nudges it. It does not filter the output: text in other scripts on the same image still comes back, and the value you passed is echoed on every row as ocr_language, which reflects your input rather than what Lens actually detected.

## `translate_to` (type: `string`):

Optional: translate the recognized text to this two-letter language code (e.g. 'en', 'nl'). Leave empty for OCR only. The translation is done by Google Lens inside the same request, not locally afterwards, so it costs no extra call. It adds to the row rather than replacing anything: full_text, the paragraphs list and the word boxes all stay in the language on the image, and the translation arrives as a separate translated_text field. Leave this blank and no translated_text key is written at all.

## `include_word_boxes` (type: `boolean`):

Emit every word with its normalized geometry (center, size, rotation). Turn off for smaller output. Switching it off only drops the words array: word_count, full_text and the per-paragraph geometry are still returned, and the request to Google is identical either way, so this changes payload size and not runtime or cost.

## Actor input object example

```json
{
  "image_urls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "ocr_language": "en",
  "translate_to": "nl",
  "include_word_boxes": true
}
```

# Actor output Schema

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

One row per image with full text, paragraphs/lines, and word-level bounding boxes.

## `output` (type: `string`):

OUTPUT record with the run's counts and status flags.

## `errors` (type: `string`):

Failures with a code and a redacted message. Absent when the run had none.

# 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 = {
    "image_urls": [
        "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
    ],
    "ocr_language": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/google-lens-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 = {
    "image_urls": ["https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"],
    "ocr_language": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/google-lens-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 '{
  "image_urls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "ocr_language": "en"
}' |
apify call s-r/google-lens-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/google-lens-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/y02xrPM5b1EGDbCTL/builds/FJxjy4a75nChFdcF1/openapi.json
