# PDF & Scan to Markdown with OCR (`ensorcelling_melilot/doc-to-markdown`) Actor

Convert PDFs, scanned documents, images and Word files to clean Markdown or plain text. Keeps headings, lists and tables, and runs OCR only on pages that have no text layer.

- **URL**: https://apify.com/ensorcelling_melilot/doc-to-markdown.md
- **Developed by:** [ensorcelling \_melilot](https://apify.com/ensorcelling_melilot) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page converteds

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

## PDF & Scan to Markdown with OCR

Turn **PDFs, scanned documents, photos of pages and Word files into clean Markdown or plain text**. Give it a list of links; get back one result per document with headings, lists and tables preserved, plus a downloadable `.md` file. Built for RAG pipelines, AI agents, search indexes and no-code workflows that need readable text instead of a binary file.

No API keys, no browser. Text PDFs are read directly (fast, accurate), and **OCR runs only on pages that have no text layer**, so you don't pay OCR prices for normal pages.

### Why this one?

- **Pay per page, and OCR only where needed.** $0.003 per page read from a text layer, $0.010 per page that needed OCR. Failed documents and skipped files are free.
- **Structure kept.** Headings (`#`, `##`), bullet and numbered lists, paragraphs rejoined across line breaks, and tables as Markdown tables.
- **Scans and photos.** PNG, JPG, TIFF, GIF, BMP and WebP images and scanned PDFs are read with Tesseract OCR in English, German, French, Spanish, Italian, Portuguese and Dutch (combine languages for mixed documents).
- **Mixed PDFs handled page by page.** A report with a few scanned pages is read from the text layer where it can be, and OCR'd only on the scanned pages. The result shows which method each page used.
- **Also reads** Word `.docx` files (headings, lists, tables) and plain text.
- **One bad document doesn't stop the run.** Every URL gets its own status and a reason if it failed (password-protected, not a PDF, too large, 404...).
- **Safe by default.** Redirects are checked hop by hop and private networks and cloud metadata addresses are refused.

### Input

| Field | What it does |
|---|---|
| Document URLs | Links to the files, one per line |
| OCR for scanned pages | Auto (default), Always, or Never |
| OCR languages | `eng` (default), `deu`, `fra`, `spa`, `ita`, `por`, `nld`; combine for mixed text |
| Output format | Markdown, plain text, or both |
| Pages to convert | e.g. `1-5,8,12-`. You pay only for converted pages |
| Include page-by-page output | Adds a `pageDetails` list with each page's text, method and size |
| Max pages per document / Max documents / Max file size | Safety limits. Larger files are skipped and not charged |
| Parallel documents | Default 2 (OCR is CPU heavy) |
| Extra request headers | Referer, Cookie etc. for sites that check them |

Example input:

```json
{
  "documentUrls": ["https://example.com/report.pdf", "https://example.com/scan.png"],
  "ocrMode": "auto",
  "ocrLanguages": ["eng"],
  "outputFormat": "markdown"
}
```

### Output

One dataset row per document:

| Field | Meaning |
|---|---|
| `status` | `converted` or `failed` (with `error`) |
| `markdown` / `text` | The converted content (documents over 2 MB of text return only the file link) |
| `markdownFile` | Link to the `.md` file in the run's key-value store |
| `pages`, `pagesTextLayer`, `pagesOcr` | Page counts and how many needed OCR |
| `characters`, `sizeBytes`, `fileType` | Size and detected type (PDF, image, docx, text) |
| `pageDetails` | Optional: per-page text, method and size |
| `pageErrors` | Pages that failed OCR, if any |

### Pricing

Pay per event:

- **Page converted:** $0.003 (text layer, Word, text)
- **OCR page:** $0.010 (scanned pages and images)

Word and text files are counted as one page per 3,000 characters. You are never charged for documents that fail.

### Good to know

- **Tables** in text PDFs become Markdown tables when their columns line up. Tables inside **scanned** pages come back as plain text rows (OCR reads text, not grid lines).
- Heading levels are guessed from text size; a document that uses bold for headings at body size will show them as normal paragraphs.
- Password-protected PDFs and `.doc` (old Word) files are not supported.
- Handwriting is not reliably read.

# Actor input Schema

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

Links to the documents to convert, one per line. PDF (text or scanned), images (PNG, JPG, TIFF, GIF, BMP, WebP), Word .docx and plain text files are supported.

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

Auto reads the text layer and runs OCR only on pages that have none (scans). Always forces OCR on every page, useful when a PDF's hidden text is wrong. Never skips OCR and only reads real text layers.

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

Languages of the scanned text. Several can be combined for mixed documents. Supported: eng, deu, fra, spa, ita, por, nld. Defaults to English.

## `outputFormat` (type: `string`):

Markdown keeps headings, lists and tables. Plain text strips the formatting. Both returns two fields.

## `pageRange` (type: `string`):

For PDFs, only convert these pages, for example 1-5,8,12-. Leave empty to convert every page. You are charged only for converted pages.

## `includePageDetails` (type: `boolean`):

Adds a pageDetails list to each result with the Markdown, the method used (text layer or OCR) and the character count for every page.

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

Stops reading a document after this many pages. A safety limit for very large files.

## `maxDocuments` (type: `integer`):

Converts at most this many of the URLs you give.

## `maxFileSizeMB` (type: `integer`):

Documents larger than this are skipped and not charged for.

## `concurrency` (type: `integer`):

How many documents are converted at the same time. OCR is CPU heavy, so a small number is best.

## `requestHeaders` (type: `object`):

Optional headers sent when downloading each document, for example a Referer or Cookie for sites that check them.

## Actor input object example

```json
{
  "documentUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "outputFormat": "markdown",
  "includePageDetails": false,
  "maxPagesPerDocument": 500,
  "maxDocuments": 100,
  "maxFileSizeMB": 50,
  "concurrency": 2
}
```

# Actor output Schema

## `documents` (type: `string`):

One row per document: status, pages (text layer and OCR), size and a link to the Markdown file.

## `allFields` (type: `string`):

Every row with the full Markdown or text.

## `markdownFiles` (type: `string`):

One .md file per converted document.

## `summary` (type: `string`):

Counts of converted documents, text pages, OCR pages and failures.

# 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": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ],
    "ocrLanguages": [
        "eng"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ensorcelling_melilot/doc-to-markdown").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": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
    "ocrLanguages": ["eng"],
}

# Run the Actor and wait for it to finish
run = client.actor("ensorcelling_melilot/doc-to-markdown").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": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "ocrLanguages": [
    "eng"
  ]
}' |
apify call ensorcelling_melilot/doc-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ensorcelling_melilot/doc-to-markdown"
        }
    }
}
```

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/cxhOf3WiOcq0iTBuQ/builds/MLMuaNkXE2EDrtenG/openapi.json
