# Document Ocr Scraper (`excellent_mustang/document-ocr-scraper`) Actor

OCR for PDFs and images that returns per-block confidence scores and coordinates, reads native PDF text layers instantly, and tells you which pages were doubtful instead of failing silently.

- **URL**: https://apify.com/excellent\_mustang/document-ocr-scraper.md
- **Developed by:** [Gorav Agarwal](https://apify.com/excellent_mustang) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.00 / 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.

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 Document Text Extractor** is an Apify Actor that reads text out of images and PDFs and returns it as structured JSON: the plain text of each page, plus every individual text block with a **confidence score** and **pixel coordinates**.

It is built for the job that starts where a web crawler stops. [Website Content Crawler](https://apify.com/apify/website-content-crawler) and similar Actors will happily download a PDF, a scanned invoice or a screenshot and hand you a file URL — but the text inside those files stays locked up. Point this Actor at those URLs and you get the contents.

Everything runs **inside the Actor**. The PP-OCR detection and recognition models ship in the Docker image and execute on ONNX Runtime on the CPU. There is no API key, no OpenAI or Google Vision account, no external OCR endpoint, and no rate limit that belongs to somebody else. Your documents are not sent anywhere.

### Main features

- **Images and PDFs** — PNG, JPEG, WebP, BMP, TIFF and PDF. Multi-page PDFs are split into pages automatically.
- **Per-block confidence and coordinates** — every detected line comes back with a `confidence` from 0 to 1 and a bounding box in page pixels, so you can filter doubtful reads instead of trusting everything equally.
- **Native PDF text layers read directly** — a PDF exported from Word or LaTeX already contains exact text. Those pages are read straight out of the file: no OCR, no transcription errors, and roughly 100× faster. Pages without a usable text layer fall through to OCR. Each result says which path it took.
- **14 scripts** — Latin, Simplified and Traditional Chinese, Japanese, Korean, Cyrillic, East Slavic, Greek, Arabic, Devanagari, Tamil, Telugu and Thai.
- **Two speed tiers** — roughly 1 second or roughly 4 seconds per A4 page, on the same multilingual architecture.
- **Nothing fails silently** — unreadable documents go to a separate `errors` dataset with an error code, a plain-English explanation and a suggested fix. Pages where no text was found carry an explicit `warning` field.
- **Deterministic** — the same file produces the same output every time. There is no site to crawl, no JavaScript to render, no anti-bot to defeat and no CAPTCHA to fail.
- **No authentication, anywhere** — the Actor accepts public URLs only, and there is nowhere to put a credential.

### How does it work?

1. **Download.** Each URL is fetched over plain HTTP. The file type is decided from its magic bytes, not from a `Content-Type` header that hosts frequently get wrong.
2. **Split into pages.** An image is one page. A PDF is opened with PDFium, and each page is checked for a real text layer before anything is rasterised.
3. **Read.** Pages with a text layer are read from the file. Everything else is rendered at your chosen DPI and passed through text detection, angle classification and text recognition.
4. **Shape.** Blocks are sorted into reading order — top to bottom, then left to right within each visual line — and written to the dataset.

The input settings are grouped to match those stages.

#### Reading the document

The **Script / language** setting is worth one moment of thought. The automatic model covers Latin alphabets, Simplified Chinese and Japanese. It does *not* cover Cyrillic, Arabic, Devanagari, Greek, Thai, Korean or Traditional Chinese, and for those it returns empty text rather than a wrong guess. Pick the matching script and the Actor loads a model trained on it.

**Image clean-up before OCR** defaults to **Deskew**, and it earns its place. A page rotated by even 5–15° — as every phone photo and fed-through-a-scanner document is — wrecks recognition while the model stays falsely confident about the garbage it produces. Measured on a document rotated by a known angle, text similarity to the upright reading recovered from **0.37–0.66 up to 0.95–1.00** once deskewed; the reading order, which a tilt scrambles into interleaved labels and values, comes back correct too. It is a no-op on pages that are already straight, so it costs almost nothing to leave on, and the angle it applied is reported per page in `skewCorrectionDeg`. **Deskew + enhance** additionally runs local contrast (CLAHE) for faded or unevenly-lit scans. Choose **None** to feed the image through untouched.

**Use the PDF text layer when there is one** is on by default and is the single biggest thing separating a cheap run from an expensive one. **PDF render DPI** defaults to 200, which suits most scans; raise it to 300 for small print or poor faxes.

#### Output

Page mode gives one row per page with the full text and a nested `blocks` array — the right shape for feeding an LLM, a RAG pipeline or a vector database. Block mode flattens to one row per detected line, which is easier to sort by confidence or open in a spreadsheet.

### Example

Running with an empty URL list OCRs the bundled sample document, so you can see the output shape before you commit anything. One page of that result:

```json
{
  "url": "https://example.com/delivery-note.png",
  "fileName": "delivery-note.png",
  "documentType": "image",
  "pageNumber": 1,
  "pageCount": 1,
  "textSource": "ocr",
  "text": "NORTHWIND SUPPLY CO.\nDelivery note DN-2026-10842\nIssued\n21 September 2026\n...",
  "blockCount": 42,
  "charCount": 552,
  "wordCount": 87,
  "meanConfidence": 0.9958,
  "minConfidence": 0.9425,
  "lowConfidenceBlockCount": 0,
  "pageWidth": 900,
  "pageHeight": 620,
  "blocks": [
    {
      "index": 0,
      "text": "NORTHWIND SUPPLY CO.",
      "confidence": 0.9645,
      "bbox": { "x": 37, "y": 35, "width": 434, "height": 33 }
    }
  ],
  "engine": "PP-OCR (RapidOCR / ONNX Runtime)",
  "quality": "balanced",
  "ocrLanguage": "auto",
  "processingMs": 1391,
  "extractedAt": "2026-09-21T01:20:14.521892Z"
}
```

`meanConfidence` is the field to watch. A clean printed page scores around 0.99. The 1776 Declaration of Independence, in period handwriting, scores 0.87 with 32 of its 103 blocks below 0.85 — the text is still largely readable, and the numbers tell you not to trust it unreviewed. That signal is the point.

### Use it with other Actors

Run [Website Content Crawler](https://apify.com/apify/website-content-crawler) with `saveContentTypes` set to `application/*` or `image/*`, take the key-value-store URLs it produces, and feed them straight into **Document URLs** here. The crawler gets you to the documents; this Actor gets you into them.

The output is already in the shape that retrieval pipelines want: one row per page, plain text in `text`, and provenance (`fileName`, `pageNumber`, `pageCount`) to attach as metadata for citations.

### How much does it cost?

The Actor is billed per result, so a page that fails costs nothing.

Beyond that you pay Apify platform usage. One compute unit is 1 GB of memory for 1 hour. On measured runs at the **balanced** tier, a full A4 page of dense print takes roughly 4–10 seconds of CPU, a small image around 1–3 seconds, and a PDF page read from its text layer is effectively free — well under 50 milliseconds. So the cheapest thing you can do is leave the text-layer option on, and the second cheapest is to use the **fast** tier when your documents are clean, large print.

Apify's free plan includes $5 of credit a month, which is plenty for testing and low-volume use.

### Troubleshooting

- If a page comes back with **empty text and a `warning`**, the page may genuinely be blank or a photograph. For PDFs, try raising **PDF render DPI** to 300. Otherwise lower **Minimum confidence** to 0.3 and inspect what comes back.
- If the text is **present but jumbled, or high-confidence yet wrong**, the page is probably tilted. Leave **Image clean-up** on **Deskew** (the default); check `skewCorrectionDeg` in the result to see the angle it corrected. For a badly rotated phone photo, deskew handles up to about 20°.
- If the text is **garbled or empty for a non-Latin script**, set **Script / language** explicitly. The automatic model returns nothing for Cyrillic, Arabic, Devanagari, Greek, Thai and Korean by design.
- If **coordinates do not line up** with your own copy of the image, remember they are in the coordinate space of the page as the Actor saw it: for PDFs that is the rendered bitmap at your chosen DPI, reported in `pageWidth`/`pageHeight`. Turn on **Save rendered page images** to get the exact image back and check against it.
- If a document is **rejected with `HTTP_STATUS_403`**, the host is probably blocking datacenter IPs. Enable Apify Proxy in the input.
- If **quality is disappointing on a phone photo**, OCR accuracy depends mostly on resolution and focus. Text smaller than about 20 pixels tall rarely reads well at any setting.
- Every failure is recorded in the **`errors`** dataset with a code, an explanation and advice — check there first.

### What this Actor does not do

It returns text, coordinates and confidence. It does not parse invoices into fields, extract named entities, classify documents, or fill a schema — you get faithful text and geometry, and the structuring is yours to do. It also does not handle handwriting well; the models are trained on printed text.

It is a general document and image text-extraction tool. It is not intended for, and should not be used to build, identity-document scanning pipelines.

### Is it legal?

Yes, for documents you are entitled to read. This Actor only fetches public URLs you supply and never accepts credentials, so it cannot reach anything behind a login. You are responsible for having the right to process the documents you submit, and for whatever personal data those documents happen to contain.

### Licensing

The Actor is MIT-licensed. It uses RapidOCR (Apache-2.0), the PaddleOCR PP-OCR models (Apache-2.0), ONNX Runtime (MIT) and PDFium via pypdfium2 (BSD-3-Clause) — all of which permit commercial use and redistribution inside a container image.

# Actor input Schema

## `documentUrls` (type: `array`):

Direct, publicly reachable links to the files you want read. Accepts PNG, JPEG, WebP, BMP, TIFF and PDF. PDFs are split into pages automatically. Leave this empty to run the bundled sample document instead - handy for a first look at the output, and it means the Actor always returns something.

## `quality` (type: `string`):

How much model to spend on each page. Both tiers use the same multilingual PP-OCRv6 architecture; the balanced tier is roughly four times slower and noticeably better on small print, faint scans and dense tables.

## `ocrLanguage` (type: `string`):

Leave on automatic unless your documents use a script the default model does not cover. The automatic model handles Latin alphabets, Simplified Chinese and Japanese. Cyrillic, Arabic, Devanagari, Greek, Thai, Korean, Tamil, Telugu and Traditional Chinese each need their own model - the automatic one returns empty text for those rather than guessing.

## `imagePreprocessing` (type: `string`):

Applied to pages that go through OCR (not to PDF text layers). **Deskew** straightens a tilted page before reading it — the single biggest accuracy win on photos and scans, because a page rotated even 5–15° wrecks recognition while the model stays falsely confident. It is a no-op on already-straight pages, so it is on by default. **Deskew + enhance** also runs local contrast (CLAHE) for faded or unevenly-lit scans. **None** feeds the image through untouched. The applied angle is reported in each result's `skewCorrectionDeg`.

## `preferEmbeddedText` (type: `boolean`):

Most PDFs that were exported from software (rather than scanned) already contain the exact text with exact coordinates. When this is on, those pages are read straight out of the file - no OCR, no guessing, no transcription errors, and about 100x faster. Pages without a usable text layer still go through OCR. Each result says which path it took in its `textSource` field. Turn this off to force OCR on every page.

## `pdfDpi` (type: `integer`):

Resolution used when rasterising a PDF page for OCR. 200 suits most scans. Raise to 300 for small print or poor-quality faxes; lower to 150 to halve the time on large, clean documents. Ignored for pages read from the text layer.

## `firstPdfPage` (type: `integer`):

Page to start from within each PDF, 1-based. Use with the page cap below to walk through a long document in chunks.

## `maxPagesPerDocument` (type: `integer`):

Hard cap on pages taken from any single PDF, so one 900-page report cannot consume the whole run.

## `minConfidence` (type: `number`):

Text blocks the model is less sure about than this are dropped. 0.5 is a sensible floor. Lower it to 0.3 to keep marginal reads on a difficult scan and filter them yourself using the `confidence` field; raise it to 0.8 when you only want text you can trust unreviewed.

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

Page mode gives you one row per page with the full text and a nested list of blocks - best for feeding an LLM or a vector database. Block mode flattens to one row per detected line, which is easier to filter, sort by confidence, or load into a spreadsheet.

## `includeBlocks` (type: `boolean`):

Attach the `blocks` array (text, confidence and bounding box for every detected line) to each page row. Turn off for a much smaller dataset when you only need the plain text. Ignored in block mode.

## `includePolygons` (type: `boolean`):

Add the raw four-corner polygon alongside the axis-aligned bounding box. Useful for rotated or skewed text; roughly doubles the size of the blocks array.

## `savePageImages` (type: `boolean`):

Store the exact image the OCR models saw in the run's key-value store, and put its URL in `pageImageUrl`. Use this to check bounding boxes against real pixels when a result looks wrong.

## `maxItems` (type: `integer`):

Total rows to write across all documents. The Actor stops cleanly when it gets here, and also stops early if the run's charging limit is reached first.

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

Downloads larger than this are rejected with a FILE\_TOO\_LARGE error rather than exhausting the run's memory.

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

How many documents to download in parallel. OCR itself always runs one page at a time, because the models already use every available core.

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

Optional. Route downloads through Apify Proxy when a file host blocks datacenter IP addresses. Not needed for most public file links.

## Actor input object example

```json
{
  "documentUrls": [],
  "quality": "balanced",
  "ocrLanguage": "auto",
  "imagePreprocessing": "deskew",
  "preferEmbeddedText": true,
  "pdfDpi": 200,
  "firstPdfPage": 1,
  "maxPagesPerDocument": 20,
  "minConfidence": 0.5,
  "outputMode": "page",
  "includeBlocks": true,
  "includePolygons": false,
  "savePageImages": false,
  "maxItems": 200,
  "maxFileSizeMb": 50,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `pages` (type: `string`):

All extracted pages with their text, per-block confidence and bounding boxes.

## `plainText` (type: `string`):

Just the file, page and text columns - the fastest thing to hand to an LLM or a vector database.

## `confidenceTriage` (type: `string`):

Pages ranked by model confidence, so the doubtful ones can be reviewed first.

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

Documents that could not be downloaded or parsed, each with an error code, an explanation and advice.

## `pageImages` (type: `string`):

The exact images the OCR models saw, stored when 'Save rendered page images' is enabled.

# 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 = {
    "documentUrls": [],
    "quality": "balanced",
    "imagePreprocessing": "deskew",
    "preferEmbeddedText": true,
    "maxPagesPerDocument": 20,
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("excellent_mustang/document-ocr-scraper").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 = {
    "documentUrls": [],
    "quality": "balanced",
    "imagePreprocessing": "deskew",
    "preferEmbeddedText": True,
    "maxPagesPerDocument": 20,
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("excellent_mustang/document-ocr-scraper").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 '{
  "documentUrls": [],
  "quality": "balanced",
  "imagePreprocessing": "deskew",
  "preferEmbeddedText": true,
  "maxPagesPerDocument": 20,
  "maxItems": 200
}' |
apify call excellent_mustang/document-ocr-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,excellent_mustang/document-ocr-scraper"
        }
    }
}
```

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/ahlpxqPVinQuv2rHy/builds/qqCsNDS5VWZhZMwKM/openapi.json
