# OCR: Image & Scanned PDF to Text (Spanish, English, Portuguese) (`isnard/ocr-image-to-text`) Actor

OCR images and scanned PDFs in Spanish, English or Portuguese and get the text plus a searchable PDF. $4 per 1,000 pages, failed files are free.

- **URL**: https://apify.com/isnard/ocr-image-to-text.md
- **Developed by:** [Matias Isnard](https://apify.com/isnard) (community)
- **Categories:** AI, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 image or page processeds

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

## OCR: Image & Scanned PDF to Text (Spanish, English, Portuguese)

OCR images and scanned PDFs in Spanish, English or Portuguese and get the text, a confidence score per line and page, and optionally a searchable PDF. Tesseract runs inside the Actor, so your files are not sent to any third-party OCR service. $4 per 1,000 pages, files that fail to download or open are free.

Give it links to images (PNG, JPG, WebP, TIFF, BMP, GIF) or scanned PDFs. For every image and every PDF page you get the recognized text, the text split into blocks and lines, a confidence score from 0 to 100 for each block, line and page, and optionally a searchable PDF.

OCR is done with [Tesseract](https://github.com/tesseract-ocr/tesseract) 5 inside the Actor. No file is sent to any third-party OCR service.

### Who it is for

- **Bookkeeping and expense tools**: get the text of receipts and invoices before parsing totals and dates.
- **Archives and research**: turn scanned documents, old newspapers and book pages into searchable text.
- **Data pipelines**: extract text from screenshots or images collected by another Actor, then search or classify it.
- **Anyone with scanned PDFs**: get a searchable PDF back without installing anything.

### Input

```json
{
    "urls": [
        "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif",
        "https://example.com/scans/contract.pdf"
    ],
    "languages": ["spa", "eng"],
    "autoRotate": true,
    "upscaleSmallImages": true,
    "threshold": false,
    "searchablePdf": false,
    "maxPdfPages": 20,
    "maxFileSizeMb": 25,
    "timeoutSecs": 60
}
```

- `urls` (required): direct links to the files. Only http and https. The link must return the file itself, not a web page that shows it. Duplicates are processed once.
- `languages` (default Spanish + English): `spa`, `eng`, `por`. Pick only the languages that appear in your files. Each extra language makes OCR slower and gives Tesseract more ways to misread a word.
- `autoRotate` (default on): detects pages turned 90, 180 or 270 degrees and turns them upright. If the rotated page reads badly (mean confidence under 70), the page is also read unrotated and the better result is kept.
- `upscaleSmallImages` (default on): images whose longest side is under 2000 px are enlarged, up to 3x. Helps with screenshots and small scans.
- `threshold` (default off): converts the page to pure black and white (Otsu) before OCR. Try it on faded scans with gray backgrounds. It can make photos with uneven light worse.
- `searchablePdf` (default off): saves one PDF per input file to the run's key-value store, with the page image and an invisible text layer you can search and copy. The link is in `searchable_pdf_url`.
- `maxPdfPages` (default 20): only the first N pages of each PDF or multi-page TIFF are processed and charged. The number of skipped pages is in `pages_skipped`.
- `maxFileSizeMb` (default 25) and `timeoutSecs` (default 60): limits for each download.

Every page is converted to grayscale before OCR. Very large images (longest side over 7000 px) are reduced. PDF pages are rendered at 300 DPI.

### Output

One dataset item per image or PDF page. Shortened real example (more in `ejemplos/salida.json` in the source):

```json
{
    "url": "https://upload.wikimedia.org/wikipedia/commons/1/10/Aviso_de_Ministerio_de_Marina_de_Diario_Oficial_de_Avisos_de_Madrid_31_Augusto_1912.jpg",
    "page": 1,
    "page_count": 1,
    "pages_skipped": 0,
    "languages": "spa+eng",
    "text": "de agua de la provincia\nde Salamanca.\n\nMinisterio de Marina.\nReales decretos concediendo\nla Gran Cruz de la Orden\ndel Mérito Naval, con dis-\n...",
    "confidence": 91.6,
    "word_count": 99,
    "blocks": [
        {
            "text": "de agua de la provincia\nde Salamanca.",
            "confidence": 96.5,
            "lines": [
                { "text": "de agua de la provincia", "confidence": 96.5 },
                { "text": "de Salamanca.", "confidence": 96.5 }
            ]
        }
    ],
    "preprocessing": { "grayscale": true, "scale": 2.642, "rotated_degrees": 0, "osd_confidence": 10.35, "threshold": false },
    "searchable_pdf_url": null,
    "error": null
}
```

- `text`: lines separated by a newline, blocks by an empty line, in Tesseract's reading order.
- `confidence`: mean of Tesseract's word confidences (0 to 100). Blocks and lines use the same formula with their own words. `null` when no text was found.
- `preprocessing.scale`: resize factor applied before OCR. `rotated_degrees`: clockwise rotation applied. `osd_confidence`: how sure the orientation detection was (`null` when it could not decide, for example on a page with almost no text).

A file that cannot be downloaded or opened gives one item with `page: null` and the reason in `error`. A page that fails during OCR gives an item with its page number and `error`. The run continues with the next page or file.

### How good is it

Measured on a test run with 15 public images, 3 pages of a scanned PDF and 3 rotated copies of test images (Spanish, English and Portuguese, languages set to Spanish + English):

- Clean prints, typed letters and official documents (including Spanish and Portuguese ones from 1891 to 1954): mean confidence 90 to 96, a few wrong characters per paragraph.
- Screenshots of web pages: confidence 89 to 92. Menus and buttons come out as short lines mixed with the content.
- Photos of receipts: confidence 54 to 67. Shop names and prices are mostly right, column alignment is lost.
- A faded dot-matrix receipt (49) and a gothic type page (39): the text is not usable.
- An 18th century broadside: confidence 79, but the long s is read as f ("Courfe" for "Course").
- Rotated pages (90, 180, 270 degrees) were turned upright and read as well as the upright originals.

Confidence is a good filter: below 60 the text usually needs a human check.

### Pricing

Pay per event: one `image-processed` event per image or PDF page that went through OCR, including pages where no text was found. Not charged: links that fail to download, files that are not images or PDFs, pages that fail during OCR, and PDF pages over `maxPdfPages`. You can cap the cost of a run with the "Maximum cost per run" setting. The Actor stops cleanly when the cap is reached.

### Limitations

- Printed text only. Handwriting is not recognized.
- Tables come out as lines of text. Columns and cell structure are not kept.
- Pages are processed one at a time. A dense full page (newspaper, legal text) takes 30 to 80 seconds at the default 1 GB of memory. More memory gives the run more CPU, so pages finish faster.
- PDFs are always rendered and OCR'd, even if they already contain a text layer.
- Language models are Tesseract's fast models (tessdata_fast), the ones Debian ships. They are slightly less accurate than the "best" models and several times faster.
- The searchable PDF shows the page after pre-processing (grayscale, enlarged or rotated if that was done), not the original image.
- Password-protected PDFs and links that need a login are not supported.

### Licenses

Tesseract and its language data: Apache-2.0. PDF rendering with [pypdfium2](https://github.com/pypdfium2-team/pypdfium2) (Apache-2.0 or BSD-3-Clause) and PDFium (BSD-3-Clause), chosen over poppler-based tools, which are GPL. Image handling with Pillow (MIT-CMU).

### Run locally

Needs Tesseract installed with the `spa`, `eng`, `por` and `osd` language data (`apt install tesseract-ocr tesseract-ocr-spa tesseract-ocr-por` on Debian or Ubuntu).

```bash
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt   # on Linux/macOS: .venv/bin/pip
## put input in storage/key_value_stores/default/INPUT.json
.venv/Scripts/python -m my_actor
python -m unittest discover -s tests -t .
```

The unit tests do not need Tesseract: its output is mocked.

Author: Matias Isnard.

### FAQ

**What is charged?**
Each image or PDF page that went through OCR, including pages with no text. Failed downloads, unsupported files and pages over `maxPdfPages` are not charged.

**Does it read handwriting or keep tables?**
No. Printed text only, and tables come out as lines.

**How fast is it?**
A dense full page takes 30 to 80 seconds at 1 GB. More memory makes it faster.

**How do I spot bad results?**
Use `confidence`. Below 60, the text usually needs a human check.

### Related tools

- [PDF to Text, Markdown & Tables (CSV)](https://apify.com/isnard/pdf-text-tables-extractor): extract text and tables from PDFs that are not scanned, faster.
- [Bulk Article Extractor: Clean Text & Markdown for LLMs](https://apify.com/isnard/article-extractor): extract text from web pages.

# Actor input Schema

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

Direct links to images (PNG, JPG, WebP, TIFF, BMP, GIF) or PDF files, one per line. Only http and https. Each image and each PDF page becomes one dataset item. Duplicates are processed once.

## `languages` (type: `array`):

Languages of the text. Pick only the ones present: every extra language makes OCR slower and can add mistakes. The first one is the main language.

## `autoRotate` (type: `boolean`):

Detect pages that are rotated 90, 180 or 270 degrees and turn them upright before OCR (Tesseract OSD). Adds a short extra step per page.

## `upscaleSmallImages` (type: `boolean`):

Enlarge images whose longest side is under 2000 px (up to 3x). Helps with screenshots and thumbnails.

## `threshold` (type: `boolean`):

Convert the page to pure black and white (Otsu threshold) before OCR. Can help with faded scans and gray backgrounds. Can hurt photos with uneven light, so it is off by default.

## `searchablePdf` (type: `boolean`):

Also save one searchable PDF per input file (page image with an invisible text layer) to the key-value store. The link is in searchable_pdf_url. The PDF shows the pre-processed (grayscale) page.

## `maxPdfPages` (type: `integer`):

Only the first N pages of each PDF (or multi-page TIFF) are processed and charged.

## `maxFileSizeMb` (type: `integer`):

Larger files are skipped with an error and not charged.

## `timeoutSecs` (type: `integer`):

Network timeout for each download.

## Actor input object example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "languages": [
    "spa",
    "eng"
  ],
  "autoRotate": true,
  "upscaleSmallImages": true,
  "threshold": false,
  "searchablePdf": false,
  "maxPdfPages": 20,
  "maxFileSizeMb": 25,
  "timeoutSecs": 60
}
```

# Actor output Schema

## `results` (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://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("isnard/ocr-image-to-text").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://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"] }

# Run the Actor and wait for it to finish
run = client.actor("isnard/ocr-image-to-text").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://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ]
}' |
apify call isnard/ocr-image-to-text --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,isnard/ocr-image-to-text"
        }
    }
}
```

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/7ot8HF8xXe5EjJoE4/builds/vwR4VLdmFPIXw3Jgf/openapi.json
