# Private OCR: Image & Scanned PDF to Text and Markdown (`nookforgestudio/private-ocr`) Actor

Extract text from images and scanned PDFs on-platform. No Google Lens, no third-party AI APIs. Returns clean text, paragraph-aware markdown and optional line boxes. Pay per page.

- **URL**: https://apify.com/nookforgestudio/private-ocr.md
- **Developed by:** [Nookforgestudio](https://apify.com/nookforgestudio) (community)
- **Categories:** AI, Developer tools, 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 page ocr’ds

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

### Private OCR: Image & Scanned PDF to Text and Markdown

Turn images and scanned PDFs into **clean text and paragraph-aware markdown**. OCR runs **inside the Actor** on Apify. Your files are **never sent to Google Lens, OpenAI or any other third-party service**, and nothing depends on scraping a website, so runs don't break when someone else's UI changes.

- 📄 **Images and PDFs**: JPG, PNG, WebP, TIFF (multi-page), BMP, GIF, and PDFs of any length
- ⚡ **Digital PDFs are exact and fast**: if a page already has a text layer, it is used directly; scanned pages are OCR'd
- 🧾 **LLM-ready output**: reading-order text plus markdown with paragraphs and headings, ready for RAG, search or an AI agent
- 📐 **Optional line boxes**: every line with `[x0, y0, x1, y1]` and confidence, for redaction, highlighting or form parsing
- 🔗 **Chain after any scraper**: point it at a dataset and the field holding image or PDF links
- 💸 **Pay per page, failed files are free**

#### Who uses it

- **Document pipelines**: invoices, receipts, contracts, scanned archives into searchable text
- **AI agents (MCP)**: give Claude, ChatGPT or Cursor a reliable "read this image/PDF" tool
- **E-commerce and listings**: read text on product photos, labels and packaging scraped by other Actors
- **Compliance-sensitive teams**: keep documents off third-party AI APIs

#### Ready-made examples

- [Extract text from receipt photos](https://apify.com/nookforgestudio/private-ocr/examples/receipt-photo-to-text)
- [Convert scanned documents to text and Markdown for AI](https://apify.com/nookforgestudio/private-ocr/examples/scanned-document-to-markdown)

#### How it compares

| | Tesseract-based OCR | Google Lens / cloud OCR APIs | **Private OCR** |
|---|---|---|---|
| Engine | Tesseract | Vendor model | **PP-OCR (deep learning), strong on photos and messy scans** |
| Files sent to a third party | No | Yes | **No, runs inside the Actor** |
| Digital PDFs | Usually re-OCR'd | Re-OCR'd | **Text layer used directly when present** |
| Output | Plain text | JSON | **Text, paragraph-aware Markdown, optional line boxes** |

#### How to use

1. Paste file links into **File URLs** (or set **Read URLs from a dataset**).
2. Pick **One row per page** or **One row per file**.
3. Click **Start**. Results appear in the **Output** tab and can be exported as JSON, CSV or Excel.

#### Input example

```json
{
  "urls": [
    "https://example.com/scans/invoice-0042.pdf",
    "https://example.com/photos/receipt.jpg"
  ],
  "outputMode": "page",
  "includeLines": false,
  "maxPagesPerPdf": 50
}
```

#### Output example

Real output for a photographed receipt (text truncated):

```json
{
  "source": "https://upload.wikimedia.org/wikipedia/commons/0/0b/ReceiptSwiss.jpg",
  "fileType": "image",
  "page": 1,
  "pageCount": 1,
  "method": "ocr",
  "width": 2448,
  "height": 3264,
  "lineCount": 18,
  "meanConfidence": 0.8027,
  "text": "Berghotel\nGrosse Scheidegg\n3818 Grindelwald\n...\n2xlatte Macchiato 4.50 CHF 9.00\n...\nTotal : CHF 54.50\n...",
  "markdown": "Berghotel\n\n## Grosse Scheidegg\n\n3818 Grindelwald\n..."
}
```

`method` is `ocr` for scanned pages and `embedded-text` when a digital PDF's own text layer was used.

#### Pricing

| Event | Price | When |
|---|---|---|
| Page OCR'd | **$0.004** | Each image or PDF page with text extracted |
| Actor start | $0.00005 per GB | Once per run |

| Example | Cost |
|---|---|
| 100 receipt photos | $0.40 |
| A 25-page scanned PDF | $0.10 |
| 1,000 pages | $4.00 |

Platform usage is included. Files that fail to download or decode are free. Cap spending with **Max pages per PDF** or the run's **maximum cost**; the Actor stops cleanly at the limit.

#### Use with AI agents (MCP)

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=nookforgestudio/private-ocr"
```

Then ask: *"Extract the text from this PDF: https://..."*

#### Use via API

```bash
curl -X POST "https://api.apify.com/v2/acts/nookforgestudio~private-ocr/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/receipt.jpg"]}'
```

#### Languages and limits

- Best on **English and Chinese** printed text, including numbers and symbols. Other Latin-script languages usually work; accents may be dropped.
- Handwriting and very stylized fonts are not reliable.
- Multi-column layouts are read line by line across columns.
- Files up to 60 MB; images larger than 4000 px on the long side are downscaled.

#### FAQ

**Are my files stored?** Only in your own run's storage on Apify, under your account's retention settings.

**Is the text for digital PDFs re-OCR'd?** No. With *Use embedded PDF text* on (default), a page's existing text layer is returned as-is when it holds at least 20 characters. Near-empty text layers are OCR'd instead.

**Need something custom?** Handwriting, forms, tables or a full document pipeline: contact [Nookforge Studio](https://apify.com/nookforgestudio).

**Something wrong with a file?** Open an issue in the **Issues** tab with the file link and the row output.

# Actor input Schema

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

Direct links to images (JPG, PNG, WebP, TIFF, BMP, GIF) or PDFs. Multi-page TIFFs and PDFs are split into pages.

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

Optional. ID of an Apify dataset (for example the output of a scraper) that contains file URLs.

## `urlField` (type: `string`):

Dot path to the field holding the URL(s), e.g. 'imageUrl', 'images' or 'product.image.url'. Arrays are expanded.

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

'page' returns one row per page. 'document' returns one row per file with all pages joined.

## `includeLines` (type: `boolean`):

Add every text line with its bounding box \[x0, y0, x1, y1] and confidence.

## `useEmbeddedPdfText` (type: `boolean`):

Digital PDFs already contain text. When on, that text is used directly (exact and fast). Scanned pages are always OCR'd.

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

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

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

Resolution for rendering scanned PDF pages. 200 suits most documents; use 300 for small print.

## `minConfidence` (type: `string`):

Drop recognized fragments below this confidence (0 to 1).

## `maxFiles` (type: `integer`):

Safety cap on the number of files per run.

## Actor input object example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/tesseract-ocr/test/main/testing/phototest.tif"
  ],
  "urlField": "url",
  "outputMode": "page",
  "includeLines": false,
  "useEmbeddedPdfText": true,
  "maxPagesPerPdf": 50,
  "pdfDpi": 200,
  "minConfidence": "0.5",
  "maxFiles": 1000
}
```

# Actor output Schema

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

One row per page (or per document in document mode) with plain text, markdown, confidence and optional line boxes.

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nookforgestudio/private-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/arc4heJAcYgCTY8Og/builds/hCRkGlkEVEErMWoJd/openapi.json
