# Image to Text OCR - Scanned PDF OCR, $2/1k Pages, Per Line (`martin10291/image-pdf-ocr`) Actor

Image to text OCR and scanned PDF OCR in bulk for $2 per 1000 images or pages. One row per image or page: text, every line with confidence and bounding box. Digital PDFs use the text layer, scans use Tesseract. 8 languages, only-new mode.

- **URL**: https://apify.com/martin10291/image-pdf-ocr.md
- **Developed by:** [Martin Petrov](https://apify.com/martin10291) (community)
- **Categories:** AI, Automation, Integrations
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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

## Image to Text OCR - Scanned PDF OCR, Per Line, Bulk

**Need the text out of images and scanned PDFs, line by line, with confidence and position?** Give this actor image or PDF links and get one clean row per image or PDF page: full text, every line with confidence and bounding box, and the method used. Digital PDFs are read from their text layer (exact, instant), scanned pages and images are read with Tesseract OCR inside the container.

No external OCR service, no API key, no proxies. Your files are downloaded once with the honest User-Agent `RadarOCR/1.0 (+apify.com/martin10291)` and processed inside the run.

### What makes this one different

- **Per-line output.** Each row has `lines` with `text`, `confidence` (0-100) and `bbox` (\[left, top, width, height] in pixels), not just one text blob. Switch to `words` for per-word boxes.
- **Scanned and digital PDFs in one actor.** Pages that already contain text are read from the text layer (`method: "text-layer"`), only pages without text go through OCR (`method: "ocr"`), so you never pay for slow OCR where it is not needed.
- **Many formats.** png, jpg, webp, tiff (multi-page TIFF = one row per page), bmp, gif, PDF.
- **Languages.** English, German, French, Spanish, Italian, Portuguese, Bulgarian, Russian, alone or combined.
- **Bulk and recurring jobs.** Pass hundreds of links. Only-new mode with a Monitor ID skips every file you already processed, so you pay for each file once.
- **Clean failures.** A broken link never stops the batch: the file is skipped, listed in `RUN_SUMMARY` with the reason, and not charged. Pages where no text is found are not delivered and not charged either.

**Honest limits:** Tesseract works best on clean printed text. Handwriting, very low resolution, heavy skew or decorative fonts give low confidence (check `meanConfidence`). No layout or table reconstruction, and no handwriting recognition. For text-layer PDF pages `confidence` and `bbox` are `null`, `width` and `height` are the page size in points; for OCR rows they are pixels of the processed image.

### Price and speed (real numbers)

| | This actor | steadydata/image-ocr-text |
|---|---|---|
| Price per image or PDF page | **$0.002** flat (free plan price) | $0.003 per image with text + $0.001 per image checked = $0.004 (free plan price, per its store page on 2026-11-04) |
| Start fee | none | $0.0005 per GB of run memory |
| Unreadable / blank pages | not delivered, not charged | failed images free, blank ones charged the $0.001 check |
| Per-line text, confidence, bbox | yes | yes |
| Scanned PDF support | yes | yes |
| Digital PDF text layer (no OCR) | yes, `method: "text-layer"` | not stated on its store page |

Measured by us on Apify, 10 real scanned book pages (1447 x 2521 px, English, from archive.org), whole run including download:

| Run memory | OCR time per dense page | Run time (10 pages) | Platform cost per page |
|---|---|---|---|
| 1024 MB | 8.2 s | 97 s | about $0.0003 |
| 2048 MB | 4.3 s | 60 s | about $0.0004 |

Small images (cover-size, about 300 x 500 px) take about 0.1 s each. Pages with a text layer need no OCR. Use 2048 MB or more for large batches of full-size scans; speed depends on page size and text density.

### Output (one row per image or PDF page)

| Field | Meaning |
|---|---|
| `sourceUrl` | The link you gave |
| `page` | 1 for images, page number for PDFs and multi-page TIFF |
| `text` | Full text of the image or page, lines joined with newlines |
| `lines` | List of `{text, confidence, bbox}` per line |
| `words` | Only in `words` mode: `{text, confidence, bbox}` per word |
| `meanConfidence` | Average line confidence (null for text-layer pages) |
| `method` | `text-layer` or `ocr` |
| `width`, `height` | Size of the image or page |

### How to use it (step by step)

1. Click **Try for free**.
2. Keep the example links or paste your own image and PDF links (one per line).
3. Pick the language of your text, then click **Start** and download JSON, CSV or Excel, or call it by API.

### API example (copy and paste)

Replace `MY_APIFY_TOKEN` with your token from Apify Console, Settings, Integrations.

```bash
curl -s -X POST "https://api.apify.com/v2/acts/martin10291~image-pdf-ocr/run-sync-get-dataset-items?token=MY_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://www.gutenberg.org/cache/epub/11/pg11.cover.medium.jpg"],"languages":["eng"],"outputMode":"lines"}'
```

### Input

| Field | Meaning |
|---|---|
| `urls` | Image and PDF links, one row per image or PDF page |
| `languages` | `eng`, `deu`, `fra`, `spa`, `ita`, `por`, `bul`, `rus` (several allowed) |
| `outputMode` | `lines` (default) or `words` |
| `maxPagesPerPdf` | Cap per PDF (default 20), controls cost |
| `forceOcr` | OCR every PDF page even if it has a text layer |
| `onlyNew` + `monitorId` | Skip files already processed with the same Monitor ID |

### Use it from AI agents (MCP)

`https://mcp.apify.com?tools=martin10291/image-pdf-ocr`

### Run summary

Every run saves `RUN_SUMMARY` in the default key-value store: counts of rows, files read, files skipped, OCR pages, text-layer pages, pages without text, total OCR seconds, and `lastError` (null when everything worked).

### Related

[Website Screenshot API](https://apify.com/martin10291/website-screenshot-api) and [URL to Markdown](https://apify.com/martin10291/url-to-markdown-llm) from the same author.

### Use it as an integration (chain it after another actor)

This actor can run automatically after any actor whose results contain an image or PDF link.

1. Open the source actor in Apify Console and go to the **Integrations** tab.
2. Click **Add integration** -> **Actor**, pick this actor and set **Dataset** to `{{resource.defaultDatasetId}}` (leave `urls` empty). Set your other options and save.
3. When the source run finishes, this actor reads its dataset and processes every row. The field is detected automatically; set **Dataset field** if your column has another name.

You can also start it by hand with `datasetId` (pick a dataset or paste its ID) to process an existing dataset.

Good sources to chain from:

- any actor that outputs imageUrl / pdfUrl / fileUrl
- martin10291/video-frame-extractor (frame images)

# Actor input Schema

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

Direct links to images (png, jpg, webp, tiff, bmp, gif) or PDF files, scanned or digital. One result row is produced per image and per PDF page. Links must be public and point straight to the file.

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

Languages of the text in your images. Pick one, or several for mixed text (a little slower). Used only when a page has to be read by OCR.

## `outputMode` (type: `string`):

lines = every text line with its confidence and bounding box. words = every single word with confidence and bounding box (bigger output).

## `maxPagesPerPdf` (type: `integer`):

Read at most this many pages from each PDF (from the first page). You pay per page, so this caps the cost of big documents.

## `forceOcr` (type: `boolean`):

By default, PDF pages that already contain a text layer are read from it (fast, exact). Turn this on to run OCR on every page anyway, for example when the text layer is wrong.

## `onlyNew` (type: `boolean`):

Skip every image or PDF URL that was already processed in an earlier run with the same Monitor ID. Use it for recurring jobs on a folder or feed of files. Each file is paid for once.

## `monitorId` (type: `string`):

Separate memory for each watch list (used with only-new mode). Use a different ID per feed or customer.

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

Optional. Pick a dataset (or paste its ID) to read the input from, for example the result of Google Maps Scraper. When this actor runs as an integration, the dataset of the triggering run is used automatically (payload.resource.defaultDatasetId), so you can leave this empty. If set, the values found in the dataset replace the main input list.

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

Optional. The item field that holds the value, for example website, url or videoUrl (dot paths like author.url work). Empty = detected automatically.

## `datasetMaxItems` (type: `integer`):

Upper limit of unique values taken from the dataset, to keep the cost under control.

## Actor input object example

```json
{
  "urls": [
    "https://www.gutenberg.org/cache/epub/11/pg11.cover.medium.jpg",
    "https://www.gutenberg.org/cache/epub/84/pg84.cover.medium.jpg",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "languages": [
    "eng"
  ],
  "outputMode": "lines",
  "maxPagesPerPdf": 20,
  "forceOcr": false,
  "onlyNew": false,
  "monitorId": "default",
  "datasetMaxItems": 1000
}
```

# Actor output Schema

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

One row per image or per PDF page: sourceUrl, page, text, lines (text, confidence, bbox), meanConfidence, method (text-layer or ocr), width, height.

# 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://www.gutenberg.org/cache/epub/11/pg11.cover.medium.jpg",
        "https://www.gutenberg.org/cache/epub/84/pg84.cover.medium.jpg",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("martin10291/image-pdf-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://www.gutenberg.org/cache/epub/11/pg11.cover.medium.jpg",
        "https://www.gutenberg.org/cache/epub/84/pg84.cover.medium.jpg",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("martin10291/image-pdf-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://www.gutenberg.org/cache/epub/11/pg11.cover.medium.jpg",
    "https://www.gutenberg.org/cache/epub/84/pg84.cover.medium.jpg",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call martin10291/image-pdf-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,martin10291/image-pdf-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/BagPzfL42cBQcQCVS/builds/QaMB66FPcROk7AG63/openapi.json
