# OCR Text Extractor - Image & Scanned PDF to Text (`luxe_tasks/ocr-text-extractor`) Actor

English OCR for PNG, JPEG, PDF. Pages are OCR'd as images; PDF text layers aren't copied. From $4/1,000 pages with text + start fee $0.005/GB RAM per run.

- **URL**: https://apify.com/luxe_tasks/ocr-text-extractor.md
- **Developed by:** [Luxe Tasks](https://apify.com/luxe_tasks) (community)
- **Categories:** AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 ocr pages

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 Text Extractor - Image & Scanned PDF to Text

Turn scanned PDFs, PNG and JPEG images into **plain English text**. You get one record per file with the text, page count and per-page word count and confidence, plus the full text as a `.txt` file. OCR runs **inside the Actor with Tesseract**: no third-party OCR service or API is called.

**Cost:** from $4 per 1,000 pages with readable text, plus a start fee of $0.005 per run at the default 1 GB of memory ($0.005 per GB, up to $0.02 at 4 GB). Blank, unreadable and failed pages have no per-page charge, but the run's start fee still applies.

**Try it:** run it with no input. It reads a built-in one-page sample (the sample page is never charged per page; Apify's per-run start fee still applies).

Every page, including PDFs that already contain selectable text, is rendered as an image and read with OCR. The Actor does not copy a PDF's embedded text layer, so output may differ from the original text (for example spacing, special characters, or small fonts). If your PDF already has selectable text, a text-extraction tool may be faster and more exact.

### Do not submit sensitive data

Do not submit protected health information (PHI) as defined under HIPAA, or consumer health data. This Actor is not HIPAA-compliant and the developer will not sign a Business Associate Agreement. Do not submit government ID documents, payment-card or bank-account numbers, or other sensitive personal data unless you have a lawful basis to process it and have assessed that this Actor is appropriate. You are the controller; the developer acts only as your processor.

### How to extract text from a scanned PDF or image

1. Upload your files to the run's **default key-value store** (recommended), or, for files you own that are already hosted, use file URL mode with the attestation.
2. In **Files**, list them as `{"key": "scan-1"}` (or `{"fileUrl": "…"}`).
3. For tables laid out in rows, set **Page layout** to `single-column`.
4. Press **Start**. Each dataset record has the text (first 100,000 characters) and a `link` to the full `.txt` file.

### Example input and output

- Input: `{}` (built-in sample) and `{ "files": [{ "key": "scan-1" }] }`.
- Output:

```json
{
  "index": 0,
  "url": "sample:sample-page.pdf",
  "sample": true,
  "status": "success",
  "ok": true,
  "key": "ocr-text-0000--d5125b66b0-txt",
  "link": "https://api.apify.com/v2/key-value-stores/<id>/records/ocr-text-0000--d5125b66b0-txt",
  "charged": false,
  "chargedEvents": 0,
  "chargeEvent": null,
  "error": null,
  "fileSha256": "ff75989d367f5a5bcab86a755e12c011c01b792a53190e5e4f6777f9c1789555",
  "fileType": "pdf",
  "fileBytes": 912,
  "pageCount": 1,
  "pagesWithText": 1,
  "pagesNotCharged": 1,
  "pages": [
    {
      "page": 1,
      "status": "ok",
      "words": 30,
      "goodWords": 30,
      "chars": 168,
      "meanConfidence": 96.1,
      "renderDpi": 200
    }
  ],
  "engine": "tesseract-native-lstm (tessdata 4.1.0)",
  "language": "eng",
  "layout": "auto",
  "text": "OCR Text Extractor sample page\n\nThis page is built into the Actor.\nIt shows what the output looks like.\nOrder 4417 shipped on 12 March.\n\nSample pages are never charged.",
  "textTruncated": false,
  "textChars": 168,
  "bytes": 168,
  "format": "txt",
  "timingMs": 1000
}
```

### Input

- **files**: a list of files, each one of
  - `{"key": "scan-1"}` - a record you uploaded to **this run's default key-value store** (recommended), or
  - `{"fileUrl": "https://example.com/doc.pdf"}` - **file URL mode**: only for documents **you own or are licensed to process**; one file per URL,
    no crawling or link following. Requires **documentRightsAttestation = true** ("I own or am licensed to process every document at these
    URLs, and fetching them does not bypass any access control."); without it the run stops before making any request.
  - URL limits: at most **50 file URLs per registered domain** and **200 file URLs per run**; any over the cap are skipped (`skipped_url_cap`),
    not fetched and not charged. URLs with a user name or password (`user:pass@host`) are refused (`invalid_input`). Presigned or query-token
    URLs are accepted; their query strings are **never logged** and appear in your results only as `?[redacted]`.
  - **Which mode to use:** upload your files to the run's default key-value store and pass their keys whenever you can. Uploads need no
    attestation, make no web requests (no robots.txt, rate-limit or domain-stop rules apply) and are not affected by the URL caps. Use file URL
    mode only for files you own or are licensed to process that are already hosted at a direct file link (for example a presigned storage URL);
    a URL that needs a login, returns a web page, or is blocked by robots.txt cannot be read and is not charged.
  - If you give no files, the Actor reads a built-in sample page to show the output; sample pages are never charged (Apify's start fee still applies).
    The sample is a synthetic one-page PDF bundled with the Actor; a run with any files never reads it.
- Accepted: PNG, JPEG, PDF (type decided by the file's bytes, not its name), up to 20 MB, 50 pages and 50 megapixels per image.
  Encrypted PDFs, archives and other types are refused (not charged).
- **language**: `eng` (Tesseract English model, tessdata 4.1.0). **layout**: `auto` (default), `single-column` (better reading order for tables and
  forms laid out in rows), `sparse`. **renderDpi** for PDF pages (100-300, default 200, capped at 25 megapixels per page).
  **pageTimeoutSecs** (default 60). **maxPagesPerFile** (default 50). **includeTextInDataset** (default true).
- Processing limits: a PDF page must render within 15 s and decode to at most 1,000,000 drawing operations; a file must finish (render and OCR)
  within 180 s. A PDF is refused before rendering (`file_bomb`) if one compressed stream expands to more than 160 MB, or if one page's content
  (its content streams and forms, counted together, whatever filters or filter chains they use) expands to more than 160 MB. It is also
  refused (`file_bomb`) if it uses a compression filter the check cannot measure (an unknown filter, or an image format used for page
  content), or if an object stream inside it expands to more than 32 MB (64 MB for all of them together). The PDF renderer
  is stopped (`render_memory_limit`: the whole file is not charged, including pages already read) if it uses more than 700 MB of memory, or if
  the run's PDF renderers and OCR processes together pass the run's memory budget and this file's renderer is the largest.
- Memory and parallel files: the number of files processed at once (`maxConcurrency`, default 3) is reduced so that the worst case fits in the
  run's memory: 1 file at 1 GB, 2 at 2 GB, 4 at 4 GB. The run log shows the requested and the effective number at start-up.
- Run limits: a run stops taking new files after 60 minutes, or once work without readable text reaches **25 files, 500 pages or 10 minutes**.
  This is counted per page, including blank or unreadable pages inside files that also have readable pages; a file in progress then stops at
  its next page (its remaining pages are `uncharged_work_limit`, readable pages already read are kept and charged), and later files are refused
  as `uncharged_work_limit`. With several files in progress at once (more than 1 GB of memory), each can finish the page it is on, so a run may
  read up to 3 pages past the limit. Files that could not be downloaded or were refused before processing do not count.
  Pages stopped by a limit are never charged; readable pages already read before a run limit stopped the file are kept and charged.

### Output

- One dataset record per file: `status`, `pageCount`, `pagesWithText`, `pagesNotCharged`, `pages` (per page: status `ok` / `blank` /
  `low_confidence` / `ocr_timeout` / `render_failed` / `page_too_large` / `page_too_complex` / `render_timeout` / `render_resource_limit` /
  `file_timeout` / `not_rendered` / `uncharged_work_limit`, word count, mean confidence), `fileSha256`, `fileType`, `text`
  (first 100,000 characters), `textChars`, `link`.
- The **full text** of each file is saved as a `.txt` record in the run's default key-value store (pages separated by a form feed `\f`).
- `RUN_SUMMARY` in the key-value store has counts and charged events.
- A run with no files writes one record for the built-in sample page, marked `sample: true` (`chargedEvents: 0`); records for your files never have this field.
- Failure statuses (never charged): `no_text_found`, `file_too_large`, `too_many_pages`, `file_bomb`, `unsupported_type`, `encrypted_file`,
  `corrupt_file`, `empty_file`, `file_not_found`, `invalid_input`, `budget_reached`, `render_timeout`, `file_timeout`, `render_resource_limit`,
  `render_memory_limit`, `run_time_limit`, `uncharged_work_limit`; URL mode also `skipped_url_cap`, `ssrf_blocked`, `robots_disallowed`,
  `blocked_or_captcha`, `rate_limited`, `skipped_blocked`, `download_failed`.

### Pricing

**Pricing: $4 per 1,000 pages with readable text (event `ocr-page`); plus a start fee per run ($0.005 at up to 1 GB of memory, +$0.005 for each extra GB). The per-page charge applies only to pages where readable text was found; Apify's per-run start fee (below) also applies.**

- Pay-per-event **`ocr-page`**: one event per page on which readable text was found in your files (the built-in sample page is never charged). Scanned and born-digital PDFs are charged the same per-page rate.
- Blank, unreadable, failed, timed-out, skipped and refused pages and files have no per-page charge.
- A multi-page file is charged all-or-nothing: if your maximum charge cannot cover all its readable pages, it is not delivered and not charged.
  Before any page of a PDF is read, the Actor checks that your remaining maximum charge covers **all** its pages; if not, the file is refused
  as `budget_reached` without OCR and not charged.
- **Start fee (what you pay even when every file fails):** $0.005 per run, through Apify's synthetic `apify-actor-start` event: "Up to and
  including 1 GB of RAM, the event is charged once. Then, it's charged once for each extra GB of memory"
  ([Apify docs](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#synthetic-start-event)). At the default 1024 MB that
  is $0.005 per run; at 2048 MB $0.01; at the 4096 MB maximum $0.02.

### Retention and logging

- Results are written **only to your run's default dataset and key-value store**; no named storages, no copies elsewhere, no temporary files
  (pages are rendered in memory and passed to Tesseract through a pipe). Apify's retention settings for your default storages apply.
- Your uploaded input files stay in your run's key-value store where you put them; the Actor does not copy them.
- The Actor **never logs document text**: logs contain status, page counts and file hashes only. Files are named in logs by their position
  ("item #3"), never by upload key, URL, host or query string.
- No data is used for training. The developer does not view or retain your files or results.

### URL mode safety (kit defaults; may only be tightened)

- robots.txt is honoured in file URL mode (no owner override in this Actor); uploads make no web requests.
- A robots.txt that returns 4xx (except 429) means no restrictions; a 429 is treated as a rate limit and stops the domain; 5xx or an unreachable robots.txt means nothing is fetched.
- At most ~1 request per second per registered domain; fixed honest User-Agent
  `OcrTextExtractorActor/0.1.0 (+https://apify.com/luxe_tasks/ocr-text-extractor; honours robots.txt)`; no proxies, logins, cookies or tokens.
- A 403, 429 (Retry-After over 60 s or missing) or CAPTCHA/challenge page stops the whole registered domain for the rest of the run
  (and after a restart); later files there are skipped, not charged.
- Private, loopback, link-local and cloud-metadata addresses are refused, re-checked after DNS and on every redirect. A URL that returns a
  web page instead of a file is refused (no crawling).

### Acceptable use

> By running this Actor you confirm that:
>
> 1. You submit only files **you own or are licensed to process**, and you will use the output lawfully.
> 2. You do not submit protected health information or consumer health data. You submit other sensitive data covered by the notice above only with a lawful basis and after assessing the Actor as appropriate.
> 3. You do not use file URL mode to get around logins, paywalls, CAPTCHAs, rate limits or other access controls, or to crawl sites.
> 4. You comply with applicable law, including copyright and data-protection law (GDPR, CCPA and similar). You are the controller of any personal data in your files and results.
> 5. You do not use the Actor to identify, profile or track individuals. The Actor does not detect, extract or match faces and is not designed to.

### Privacy

> Results are written only to your run's default Apify storage. The developer does not copy, view or retain them. If your results contain
> personal data, you are the controller and the developer acts only as your processor under Apify's Standard Actor Contract. Apify's retention settings apply.

### Accuracy (synthetic test corpus, local run)

On 12 synthetic pages (clean 100-300 dpi scans, a noisy rotated JPEG scan, a raster PDF and a vector PDF), character error rate was 0.0 on
10 pages. The 8-pt text page at 100 dpi scored 98.1% word recall (CER 0.27%, WER 1.9%). A table-like page laid out in columns scored 98.2%
word recall but CER 57.8% / WER 68.8% with `layout: auto`, because the text is read column by column (reading order); use
`layout: single-column` for row order. Real documents vary: test on your own samples.

### Third-party software

Tesseract OCR, its tessdata English model and PDF.js (Apache-2.0), @napi-rs/canvas (MIT); details and licence texts in `NOTICE` and `licenses/`.

### Runtime

Node 24 (`engines`, `.nvmrc`, Dockerfile `apify/actor-node:24` + Alpine `tesseract-ocr` 5.5.2). Memory: default and minimum **1024 MB**, maximum
4096 MB; files processed in parallel = `maxConcurrency`, reduced to what fits in memory (1 at 1 GB, 2 at 2 GB, 4 at 4 GB; see `src/memory.js`). Tests: `npm test`. Local reliability and accuracy runs:
`python3 test/reliability/ocr_runs.py` (fixture server on 127.0.0.1 only). Benchmarks: `node bench/ocr_engines.mjs`, `python3 bench/ocr_bench.py`.

### FAQ

**Is my document sent to a third-party OCR API?** No. OCR Text Extractor runs Tesseract inside the Actor, and results go only to your run's default Apify storage.

**How much does it cost to OCR 100 pages?** From $4 per 1,000 pages with readable text plus the start fee of $0.005 per run at the default 1 GB, so 100 readable pages in one run cost $0.40 plus the $0.005 start fee ($0.405).

**Does it copy the text layer of a digital PDF?** No. OCR Text Extractor renders every page as an image and reads it with OCR, so output can differ from the original text. For PDFs that already have selectable text, a text-extraction tool may be faster and more exact.

**Which languages are supported?** English only (Tesseract `eng` model).

**Can I OCR health records or ID documents?** Do not submit protected health information or consumer health data. Do not submit government ID documents or other sensitive personal data unless you have a lawful basis to process it. See "Do not submit sensitive data".

### Changelog

See `CHANGELOG.md`.

### More tools from Luxe Tasks

| Tool | What it does |
|---|---|
| [Website Screenshot & URL to PDF Converter](https://apify.com/luxe_tasks/screenshot-pdf) | PNG/JPEG screenshots (viewport or full page) and PDFs of public web pages you choose. |
| [Tech Stack Detector](https://apify.com/luxe_tasks/tech-stack-detector) | Finds the CMS, JavaScript frameworks, analytics, CDN and web server a public website uses, with evidence. |
| [HTTP API Request Runner](https://apify.com/luxe_tasks/http-runner) | Calls APIs you are authorized to use, with your own key kept in an encrypted secret input field. |

Each tool is priced per result plus a per-run start fee; see that tool's Pricing section.

Developer: Luxe_tasks.

Contact: https://apify.com/luxe_tasks · Issues tab on the actor page.

# Changelog

This Actor's version history is a separate document: https://apify.com/luxe_tasks/ocr-text-extractor/changelog.md

# Actor input Schema

## `files` (type: `array`):

PNG, JPEG or PDF files (max 20 MB, 50 pages each). Uploads: \[{"key": "scan-1.pdf"}] for a record in this run's default key-value store. File URLs: \[{"fileUrl": "https://example.com/doc.pdf"}] - only documents you own or are licensed to process, one file per URL, no crawling; needs the attestation below. At most 50 file URLs per registered domain and 200 per run (more are skipped, not charged). URLs with a user name or password (user:pass@host) are refused; presigned URLs are fine and their query strings are never logged. Leave empty to read a built-in sample page instead (shows the output; sample pages are never charged).

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

OCR language model (Tesseract tessdata, English). English only in this version.

## `layout` (type: `string`):

auto = automatic page segmentation; single-column = one block of text; sparse = scattered text (forms, labels).

## `renderDpi` (type: `integer`):

PDF pages are rendered at this resolution before OCR (capped at 25 megapixels per page).

## `pageTimeoutSecs` (type: `integer`):

A page that takes longer is reported as ocr_timeout and not charged.

## `maxPagesPerFile` (type: `integer`):

PDFs with more pages are refused (too_many_pages), not charged.

## `includeTextInDataset` (type: `boolean`):

Also copy the text (first 100,000 characters) into each dataset record. The full text is always saved as a .txt file in the run's key-value store.

## `documentRightsAttestation` (type: `boolean`):

Required only when you give file URLs. Leave unticked for uploads.

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

Items processed in parallel (each site still gets at most 1 request per second).

## Actor input object example

```json
{
  "language": "eng",
  "layout": "auto",
  "renderDpi": 200,
  "pageTimeoutSecs": 60,
  "maxPagesPerFile": 50,
  "includeTextInDataset": true,
  "documentRightsAttestation": false,
  "maxConcurrency": 3
}
```

# Actor output Schema

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

Default dataset, one record per input file, shown in the 'Results' view: index, inputKey, url (query string redacted), sample (only on the built-in sample record), status (success or the failure reason), pageCount, pagesWithText, chargedEvents, textChars, link (public URL of the .txt record; null when no text was saved) and error. The 'Text' view adds the recognised text. Failed files and pages without readable text are not charged.

## `texts` (type: `string`):

Default key-value store. The full text of each file with readable text is a record named ocr-text-<index>-<host>-<hash>-txt (text/plain; pages separated by a form feed). The store also holds INPUT, any files you uploaded there, RUN_SUMMARY (counts and charged events) and STATE (progress record used on restart).

## `runSummary` (type: `string`):

RUN_SUMMARY record: totals by status and the charged events for the run.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("luxe_tasks/ocr-text-extractor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("luxe_tasks/ocr-text-extractor").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 '{}' |
apify call luxe_tasks/ocr-text-extractor --silent --output-dataset

```

## MCP server setup

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

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/OUO1NiLWCUMguo56V/builds/wL4SD7v6eAX7s0Yfo/openapi.json
