# PDF & Image Text Extractor: OCR to Text & Markdown (`subtleasianmerch/document-text-extractor`) Actor

Extract text and Markdown from PDFs and images (PNG, JPEG, TIFF, WebP, GIF, BMP, HEIC) by link. Uses a PDF's own text where it has some and Tesseract OCR for scans and photos, in 124 languages. One row per page with word counts and OCR confidence. Failed files and blank pages are free.

- **URL**: https://apify.com/subtleasianmerch/document-text-extractor.md
- **Developed by:** [Public Data Tools](https://apify.com/subtleasianmerch) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 page read with ocrs

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

## PDF & Image Text Extractor: OCR to Text and Markdown

Extract the text from PDFs, scanned documents and images, page by page, as plain text and simple Markdown. Give links to your files: where a PDF page has its own (embedded) text, that text is used, exactly and fast; scanned pages, pages without usable text, and images are read with Tesseract OCR in any of 124 languages. Each page comes back as one row with its text, Markdown, word and character counts, how the text was obtained, the OCR confidence and the page size.

- 📄 **Text PDFs and scans in one run:** the PDF's own text where a page has it, OCR where it doesn't, decided page by page. Or always OCR, or never.
- 🖼️ **Images too:** PNG, JPEG, TIFF (every page), WebP, GIF, BMP, HEIC, AVIF and JPEG 2000. Sideways phone photos are turned upright.
- 🌍 **124 OCR languages** (Tesseract 5): English and 17 European languages are installed; Chinese, Japanese, Arabic, Hindi and the rest are added when a run uses them.
- 📝 **One row per page:** text, Markdown (headings, paragraphs, lists), word and character counts, text source, OCR confidence, page size.
- 🗂️ **One row per file too, if you like:** all of its text and Markdown, the PDF's title, author and dates, and the whole text as .txt and .md files.
- 🔒 **Your files only:** it reads only what you link to, keeps nothing outside your run's own storage, and never writes document text to the log.
- 💸 **$0.50 per 1,000 pages of PDF text, $3 per 1,000 OCR pages.** Files that can't be read, password-protected files and blank pages are free.

**Try it:** the input comes pre-filled with the US federal tax form W-4 (a 5-page PDF from irs.gov) and a printed copy of the Emancipation Proclamation from 1863 (an image on Wikimedia Commons). Both are public domain. Click **Start** to get 5 pages of PDF text and 1 page of OCR text, then add your own files.

### Who it's for

- **AI and RAG teams:** turn document collections into clean page-by-page text and Markdown for search, embeddings and chatbots.
- **Back offices:** pull the text out of scanned contracts, invoices, letters and forms so it can be searched and checked.
- **Researchers, archivists and librarians:** make scanned books, reports and historical documents searchable.
- **Developers:** call it through the Apify API instead of running poppler and Tesseract yourself.
- **Data teams:** process many files in one run, then export the results as JSON, CSV or Excel.

### What you get

**For every page:**

- the text (`text`): the PDF's embedded text in reading order, or the OCR text, with paragraphs separated by blank lines;
- a simple Markdown version (`markdown`): paragraphs, headings (text clearly larger than the body text) and bullet or numbered lists;
- word and character counts;
- how the text was obtained (`textSource`: `embedded` or `ocr`), the OCR languages and Tesseract's confidence (0–100);
- the page size: points for PDF pages (with A4, Letter and other standard names), pixels for images;
- the file's link, name, type, page number and page count.

**For every file,** the run's `RUN_SUMMARY` record lists its type and size, page count, pages read, pages with text, blank and failed pages, how many were read by OCR and their mean confidence, the PDF's title, author, subject, keywords, creator, producer, creation and modification dates, PDF version and encryption, or the reason it couldn't be read. Turn on **One row per file too** to get the same as a dataset row, with all the file's text and Markdown, and the whole text saved as `.txt` and `.md` files in the run's storage.

**Fair charging:** you pay per page with text. Free: links that aren't PDFs or images (web pages, Word files, text files), missing files (404), refused access (403, an expired link), bot checks, damaged files, password-protected PDFs, PDFs that forbid copying their text, blank pages, and pages beyond your limits. A run stops cleanly at your maximum cost.

### How to use it

#### In Apify Console

1. Paste your **file links**, one per line. The link must download the file itself, for example `https://example.com/report.pdf`. Or upload files from your computer with **Or upload files**.
2. Optionally choose the **pages** (for example `1-5, 8, 10-`), the **most pages per file** (500 by default) and the **most pages per run**.
3. Choose the **OCR languages** of your documents (English by default; pick all that appear) and the **OCR** mode: automatic (embedded text first), always or never.
4. Click **Start**. Open the **Pages** view to read the text, the **Markdown** view for Markdown, or download the results as JSON, CSV or Excel.

#### Files on S3, Google Cloud Storage, Dropbox, Google Drive or Apify

- **S3 or Google Cloud Storage:** a public object link, or a signed (pre-signed) link that is valid long enough for the run.
- **Dropbox:** a shared link. The Actor changes `?dl=0` to `?dl=1`, so it downloads the file instead of the preview page.
- **Google Drive:** a "share" link to a file anyone with the link can view (`drive.google.com/file/d/…/view`). The Actor turns it into Drive's download link. If Drive answers with its virus-scan page instead (it does for large files), the link is listed as a web page, free.
- **GitHub:** a link to a file on github.com (`…/blob/…`) is turned into its raw link.
- **Apify key-value store:** the record's link. Links without a signature are opened with the run's own Apify token; if Apify refuses, use the record's signed link (with `?signature=`).

#### With the Apify API

```bash
curl -X POST "https://api.apify.com/v2/acts/subtleasianmerch~document-text-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fileUrls": ["https://example.com/scan.pdf"], "ocrLanguages": ["eng", "deu"]}'
```

### Example input

```json
{
  "fileUrls": [
    "https://example.com/contracts/lease-2025.pdf",
    "https://my-bucket.s3.amazonaws.com/scans/letter.tif",
    "https://example.com/photos/receipt.jpg"
  ],
  "pages": "1-20",
  "maxPagesPerFile": 20,
  "ocrMode": "auto",
  "ocrLanguages": ["eng", "fra"],
  "ocrLayout": "auto",
  "ocrDpi": 300,
  "markdown": true,
  "fileRows": true
}
```

### Output

One row per page. A real row from a test run on Apify (3 November 2026, the pre-filled example), with the text and Markdown shortened:

```json
{
  "rowType": "page",
  "fileUrl": "https://upload.wikimedia.org/wikipedia/commons/4/4d/Emancipation_proclamation_typeset_signed.jpg",
  "fileIndex": 2,
  "fileName": "Emancipation_proclamation_typeset_signed.jpg",
  "fileType": "jpeg",
  "pageNumber": 1,
  "pageCount": 1,
  "status": "ok",
  "text": "BY THE PRESIDENT OF THE UNITED STATES OF AMERICA,\n\nA Proclamation.\n\nAM Tvereas, on the twenty-second day of September, in the year of our Lord one thou-\nsand eight hundred and sixty-two, a proclamation was issued hy the President of the United\nStates, …",
  "markdown": "### BY THE PRESIDENT OF THE UNITED STATES OF AMERICA,\n\n# A Proclamation.\n\nAM Tvereas, on the twenty-second day of September, in the year of our Lord one thousand eight hundred and sixty-two, a proclamation was issued hy the President of the United States, …",
  "wordCount": 765,
  "charCount": 4521,
  "textSource": "ocr",
  "ocrConfidence": 91.7,
  "ocrLanguages": "eng",
  "pageWidth": 1457,
  "pageHeight": 2567,
  "pageSizeUnit": "px",
  "pageSizeName": null,
  "error": null
}
```

The OCR text keeps the misreadings Tesseract makes ("AM Tvereas" is the decorated "Whereas" of the original, "hy" is "by"). A PDF page with embedded text has `"textSource": "embedded"`, no confidence, and its size in points, for example `612` × `792` and `"pageSizeName": "Letter"`.

Rows for pages without text have `status` `blank`; rows for files that couldn't be read have `rowType` `file`, a `status` such as `failed`, `blocked`, `unsupported`, `protected` or `restricted`, and the reason in `error`. They are free.

### Pricing

- **$0.0005 per page of PDF text** ($0.50 per 1,000): pages whose text comes from the PDF itself.
- **$0.003 per OCR page** ($3 per 1,000): scanned PDF pages, and each image or TIFF page.

A 40-page text PDF costs $0.02; a 100-page scanned PDF $0.30; 1,000 receipt photos $3.

Free: files that can't be downloaded or read, links that aren't PDFs or images, password-protected and copy-restricted PDFs, blank pages, pages beyond your limits, and pages stopped by your maximum cost. Apify's platform usage is included in the price.

### Good to know

- **Limits.** Files up to 200 MB each; up to 1,000 files and, by default, 500 pages per file (up to 10,000) in a run. Images up to 200 megapixels; images larger than 7,000 pixels on a side (or 40 megapixels) are scaled down for OCR.
- **OCR quality depends on the document.** Tesseract reads clean printed text well. In tests: a 1902 Dutch book scan had a confidence of 92.5, an 1863 printed proclamation 91.7, a 1918 newspaper front page 86.5, an ornate 1776 broadside 78, full-page website screenshots 74–85, and a 1946 Japanese government gazette in vertical type only 52. Handwriting isn't supported. Choose the right languages: OCR with the wrong language misreads accented letters.
- **Speed.** At the default memory (2 GB), a scanned book page takes about 4 seconds and a dense newspaper page about a minute. Text PDFs are fast: 59 pages took 5 seconds. 4 GB reads OCR pages about 1.5 times as fast for about 1.4 times the platform cost (the price per page is the same for you).
- **Embedded text is used as the PDF gives it,** in pdftotext's reading order. Some PDFs have garbled embedded text (fonts without a Unicode map): choose **Always OCR** for those. Scanned pages with only a little embedded text (such as a stamp or a page number) are read with OCR too, and the OCR text is used when it finds more.
- **Markdown is simple:** paragraphs, headings and lists. Tables come out as lines of text, not Markdown tables.
- **Password-protected PDFs aren't opened** (there's no password input), and **PDFs that forbid copying their text** (their owner set "copy: no") are respected and listed free. Both are listed with the reason.
- **Direct links only.** A link that opens a web page or a viewer isn't read (free); give the link that downloads the file. The Actor identifies itself honestly, doesn't get past bot checks, and refuses links to private or internal network addresses.
- **Privacy.** Documents may contain personal data. The Actor processes only the files you give it, downloads each one to the run's temporary disk and deletes it when that file is done, keeps the results only in your run's own storage (as long as your plan keeps it), and never writes document text to the log. Language models are the only thing it downloads besides your files (from Tesseract's official repository, when you choose a language that isn't installed).

### FAQ

**Does it read scanned PDFs?** Yes. Pages without usable embedded text are rendered at 300 dpi and read with OCR. PDFs that already have a hidden OCR text layer ("searchable" scans) use that text, which is faster and free of a second OCR's errors.

**Which languages can it read?** Any of Tesseract's 124: English, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Swedish, Danish, Norwegian, Finnish, Czech, Romanian, Hungarian, Turkish, Russian and Ukrainian are installed; the others (Chinese, Japanese, Korean, Arabic, Hebrew, Hindi, Greek, Thai and more) take a few seconds more on first use in a run.

**Can it read Word, Excel or PowerPoint files?** No. Save them as PDF first. Such files are listed as unsupported, free.

**Am I charged for blank pages?** No. Pages with no readable text, and OCR pages where Tesseract isn't at least 50% sure of any word, are listed as blank and free.

**Can I upload a file from my computer?** Yes: **Or upload files** in the input stores the file in one of your Apify key-value stores, and the Actor reads it from there. If Apify refuses the run access to that store, use the record's signed link (with `?signature=`) under **File links** instead.

**Does it work for handwriting?** No. Tesseract reads printed and typed text.

### Related Actors

More Actors from the same developer that work well with this one:

| Actor | Use it to |
|---|---|
| [Website Screenshot API](https://apify.com/subtleasianmerch/website-screenshot-api) | save web pages as PDFs or images, then extract their text here |

Questions, bugs or ideas? Open an issue on this Actor's **Issues** tab.

# Actor input Schema

## `fileUrls` (type: `array`):

Direct links to PDFs and images, one per line: PDF, PNG, JPEG, TIFF (all its pages), WebP, GIF, BMP, HEIC/HEIF, AVIF or JPEG 2000. The link must download the file itself, e.g. https://example.com/report.pdf: your own server, a public or signed S3 or Google Cloud Storage link, a Dropbox or Google Drive share link (changed to its download link for you), or a record in Apify storage. Files up to 200 MB.

## `uploadedFiles` (type: `array`):

Optional. Files from your computer, stored in one of your Apify key-value stores and read from there. Read together with the links above.

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

Optional. Which pages of each PDF (and multi-page TIFF) to read, e.g. 1-5, 8, 10- (from page 10 to the end). Empty reads every page. Single images are always read.

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

No file gives more pages than this; the rest aren't read and cost nothing.

## `maxPagesPerRun` (type: `integer`):

Optional. The run stops after this many pages with text in all. It also stops cleanly at your maximum cost per run.

## `ocrMode` (type: `string`):

Automatic: a PDF page's own (embedded) text is used where it has some; scanned pages, pages without usable text, and images are read with OCR (Tesseract). Always: every PDF page is read with OCR, ignoring its embedded text (for PDFs whose text is garbled). Never: only embedded text; pages without it, and images, give no text (free).

## `ocrLanguages` (type: `array`):

The languages in your documents, for OCR (up to 5; each one makes OCR slower). Choose all that appear, e.g. English and French for a bilingual form. 18 common languages are installed; the others (Chinese, Japanese, Arabic, Hindi and 102 more) are downloaded from Tesseract's official models when a run uses them, which adds a few seconds.

## `ocrLayout` (type: `string`):

How Tesseract finds the text on a page. Automatic suits most documents, including several columns. Single column reads the page as one column of text. Single block treats the page as one block (receipts, labels, cropped text). Sparse finds as much scattered text as it can, in no particular order (forms, diagrams, screenshots).

## `ocrDpi` (type: `integer`):

PDF pages are turned into images at this resolution for OCR. 300 suits most documents; 400 can help with very small print, and is slower. Images are read at their own resolution.

## `markdown` (type: `boolean`):

Also give each page as simple Markdown: paragraphs, headings (text clearly larger than the body text) and lists. Tables come out as plain lines.

## `fileRows` (type: `boolean`):

Also add one row per file with all its text and Markdown, its page counts and the PDF's title, author and dates, and save the whole text as .txt and .md files in the run's storage. These rows are free.

## Actor input object example

```json
{
  "fileUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw4.pdf",
    "https://upload.wikimedia.org/wikipedia/commons/4/4d/Emancipation_proclamation_typeset_signed.jpg"
  ],
  "maxPagesPerFile": 500,
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "ocrLayout": "auto",
  "ocrDpi": 300,
  "markdown": true,
  "fileRows": false
}
```

# Actor output Schema

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

The dataset rows this run produced.

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

Counts, costs and anything skipped, saved as RUN_SUMMARY.

## `files` (type: `string`):

Files saved in the run's key-value store; each dataset row links to its own.

# 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 = {
    "fileUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw4.pdf",
        "https://upload.wikimedia.org/wikipedia/commons/4/4d/Emancipation_proclamation_typeset_signed.jpg"
    ],
    "ocrMode": "auto",
    "ocrLanguages": [
        "eng"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("subtleasianmerch/document-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 = {
    "fileUrls": [
        "https://www.irs.gov/pub/irs-pdf/fw4.pdf",
        "https://upload.wikimedia.org/wikipedia/commons/4/4d/Emancipation_proclamation_typeset_signed.jpg",
    ],
    "ocrMode": "auto",
    "ocrLanguages": ["eng"],
}

# Run the Actor and wait for it to finish
run = client.actor("subtleasianmerch/document-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 '{
  "fileUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw4.pdf",
    "https://upload.wikimedia.org/wikipedia/commons/4/4d/Emancipation_proclamation_typeset_signed.jpg"
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ]
}' |
apify call subtleasianmerch/document-text-extractor --silent --output-dataset

```

## MCP server setup

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