# Document to Markdown & Text: PDF, OCR, Word, PowerPoint (`webstack-labs/document-to-markdown`) Actor

Convert PDFs, scans, images, DOCX, PPTX, XLSX and HTML to clean Markdown and text for LLMs and RAG. Tables become Markdown tables; scanned pages are read with Tesseract OCR (24 languages). $0.004 per document, $0.002 per OCR page or image; failed files free.

- **URL**: https://apify.com/webstack-labs/document-to-markdown.md
- **Developed by:** [Webstack Labs](https://apify.com/webstack-labs) (community)
- **Categories:** AI, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 document 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

## Document to Markdown & Text: PDF, OCR, Word, PowerPoint

Turn PDFs, scanned pages, images, Word, PowerPoint, Excel and HTML files into **clean Markdown and plain
text** that you can feed straight into an LLM, a RAG pipeline or a search index. Tables come out as
**Markdown tables**, and pages without a text layer (scans, photos of documents) are read with
**Tesseract OCR in 24 languages** (image to text OCR for photos, screenshots and scans). One row per file, failed files are free.

### What it does

- **PDF with a text layer**: extracts the text page by page (pypdfium2) and detects tables (pdfplumber),
  which are returned as Markdown tables in reading order.
- **Scanned PDF pages and images** (PNG, JPEG, TIFF incl. multi-page, WebP, BMP, GIF): OCR with Tesseract,
  with a confidence score per page. In `auto` mode only the pages that need it are OCR'd, so you never pay
  OCR for a page that already has text.
- **Word (DOCX)**: headings (`#`), lists (`-`), paragraphs and tables in document order.
- **PowerPoint (PPTX)**: one `## Slide N: title` section per slide, bullet points, tables, speaker notes.
- **Excel (XLSX)**: one Markdown table per sheet (cell values, not formulas).
- **HTML, CSV, TXT, Markdown**: main content to Markdown (scripts, menus and forms removed), CSV to a table.
- Every result is also saved as a `.md` file in the run's key-value store (`markdownUrl`).

Everything runs inside the Actor on Apify: your files are **not sent to any third-party AI or OCR API**.

### Who uses it

- Developers building **RAG / chatbots / AI agents** that need documents as Markdown chunks.
- Teams digitising **scans, invoices, receipts and forms** (text only; no field extraction).
- Automations in Make, n8n, Zapier or LangChain that receive files as links.

### Input

```json
{
  "fileUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://example.com/scanned-contract.pdf",
    "https://example.com/slides.pptx"
  ],
  "ocr": "auto",
  "ocrLanguages": ["eng", "deu"],
  "pageRange": "1-20"
}
```

| Field | Default | What it does |
|---|---|---|
| `fileUrls` | – | Direct links to the files. Empty: a free 2-page sample PDF is converted. |
| `ocr` | `auto` | `auto`: OCR only pages without a text layer, and images. `always`: OCR every PDF page. `never`: no OCR. |
| `ocrLanguages` | `["eng"]` | 1–4 languages: eng, deu, fra, spa, ita, por, nld, pol, rus, ukr, tur, jpn, chi_sim, chi_tra, kor, ara, hin, vie, ind, swe, dan, nor, fin, ces (ISO codes like `de`, `ja` work too). |
| `pageRange` | all | Pages or slides, e.g. `1-5, 8, 12-`. |
| `maxPagesPerFile` | 200 | Stop after this many pages of one file (row marked `truncated`). |
| `extractTables` | true | Tables on PDF text pages as Markdown tables. |
| `includePages` | true | Per-page text, OCR flag and confidence in `pages`. |
| `includePlainText` | true | Plain text in `text` (Markdown is always returned). |
| `ocrDpi` | 200 | Render resolution for scanned pages (100–400). |
| `maxFileSizeMb` | 100 | Larger files are skipped for free. |
| `respectRobotsTxt` | true | Check the site's robots.txt before downloading. |

### Output

One row per file (every row has the same fields; unknown values are `null`):

```json
{
  "url": "sample",
  "fileName": "sample.pdf",
  "fileType": "pdf",
  "status": "ok",
  "title": "Sample document",
  "pageCount": 2,
  "pagesConverted": 2,
  "ocrPages": 1,
  "ocrLanguages": "eng",
  "ocrConfidence": 95.8,
  "tables": 1,
  "markdown": "# Sample document\n\n<!-- page 1 -->\n\nQuarterly report (sample)\n...\n\n| Region | Revenue | Growth |\n| --- | --- | --- |\n| Europe | 120 | 8% |\n| Asia | 95 | 12% |\n\n<!-- page 2 -->\n\nScanned page (sample)\n\nThis page is a picture of text, like a scan or a photo.\n...",
  "text": "Quarterly report (sample)\n...",
  "pages": [
    {"page": 1, "text": "Quarterly report (sample)...", "ocr": false, "ocrConfidence": null, "tables": 1, "charCount": 254},
    {"page": 2, "text": "Scanned page (sample)...", "ocr": true, "ocrConfidence": 95.8, "tables": 0, "charCount": 190}
  ],
  "markdownUrl": "https://api.apify.com/v2/key-value-stores/<id>/records/doc-00001-sample.md",
  "billedDocument": 0,
  "billedOcrPages": 0,
  "error": null
}
```

Failed files get a row with `status: "failed"`, an `error` code (`blocked_by_robots_txt`, `http_error`,
`file_too_large`, `unsupported_file_type`, `password_protected`, `unreadable_pdf`, `no_text_found`, ...)
and a plain `errorMessage`. They are not charged. A run summary is saved as `SUMMARY` in the key-value store.

### Pricing

Pay per event, no subscription:

- **$0.004 per document**: a PDF, Word, PowerPoint, Excel, HTML, CSV or text file converted, with all its
  text-layer pages ($4 per 1,000 documents).
- **$0.002 per OCR page**: an image, or a scanned PDF page without a text layer ($2 per 1,000 pages).
- Plus Apify's small run start fee. Failed files and the built-in sample are free.

Examples: a 30-page digital PDF = $0.004. A single photo or screenshot = $0.002. A 10-page scanned PDF =
$0.004 + 10 × $0.002 = $0.024. You can cap the cost of a run with Apify's "maximum cost per run" setting;
the Actor stops when it is reached.

### Use it from AI agents (MCP)

The Actor is available to AI agents (Claude, ChatGPT, Cursor and others) through Apify's MCP server
(`mcp.apify.com`) and the API. Minimal input:

```json
{"fileUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]}
```

First rows of the output: `{"fileType": "pdf", "status": "ok", "pagesConverted": 1, "ocrPages": 0,
"markdown": "<!-- page 1 -->\n\nDummy PDF file", ...}`. Cost of this example: $0.004 plus the start fee.
Read `markdown` (or `markdownUrl` for long documents); check `status` and `error` on every row.

### How it compares

Image to text OCR, PDF text extraction and Office conversion usually need two or three different Actors.
This one does all three with one row shape. Other popular Actors, as described on their Store pages on
2026-11-05 (check their pages for changes):

| | This Actor | Image-OCR Actors (e.g. steadydata/image-ocr-text, zenomastro/image-ocr-text-extractor) | PDF extractors (e.g. memo23/pdf-text-extractor) |
|---|---|---|---|
| Images (PNG, JPEG, TIFF, WebP …) | yes, OCR | yes | no |
| Scanned PDFs | yes, OCR only on pages without text | yes | yes, OCR only where needed |
| PDFs with a text layer | yes, one fee per document | OCR'd page by page | yes |
| Word, PowerPoint, Excel, HTML, CSV | yes | not mentioned | no |
| Tables | Markdown tables | text, line or word boxes | text lines |
| Markdown output | always, plus a `.md` file | optional or not mentioned | yes |
| Price | $0.004 per document, $0.002 per OCR page or image | $0.00195–0.005 per image or page | from $0.005 per PDF plus OCR pages |

Choose an image-OCR Actor if you need word or line bounding boxes or more than 24 languages; choose this
one when the input is a mix of PDFs, scans and Office files and the output goes to an LLM or RAG pipeline.

### How it works

1. Downloads each link (robots.txt respected by default, honest User-Agent, size and time limits).
2. Detects the real file type from its content, not only the extension.
3. Converts it: text layer first, OCR only where needed, tables kept as tables.
4. Saves one dataset row and one `.md` file per document.

### FAQ

**Does it keep headings in PDFs?** Word, PowerPoint and HTML headings are kept. PDFs carry no reliable
heading information, so PDF text is returned as paragraphs and tables, with page markers.

**Handwriting?** Tesseract reads printed text well; handwriting poorly.

**Password-protected PDFs?** Not supported (free failed row).

**Can I upload files instead of links?** Use any direct link: a file in your own storage, an Apify
key-value store record, or a signed URL. Pages of file-sharing viewers (not the file itself) are not supported.

### Limitations

- Layout-heavy PDFs (multi-column magazines) can come out in a mixed reading order.
- Table detection on PDFs is heuristic; tables without ruling lines may stay as text.
- Images inside PDFs that also have a text layer are not OCR'd in `auto` mode (use `ocr: "always"`).
- Excel formulas are returned as their last saved values.

### Credits

Tesseract OCR (Apache-2.0), pypdfium2 / PDFium (Apache-2.0, BSD-3-Clause), pdfplumber and pdfminer.six (MIT),
python-docx, python-pptx, openpyxl, BeautifulSoup, markdownify (MIT), Pillow (HPND).

# Actor input Schema

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

Direct links to the files to convert: PDF (text or scanned), PNG, JPEG, TIFF, WebP, BMP, GIF, DOCX, PPTX, XLSX, HTML, CSV, TXT or Markdown. One row per file. Leave empty to convert a free 2-page sample PDF and see the output.

## `ocr` (type: `string`):

When to read pages with OCR (Tesseract). auto (default): only PDF pages with no usable text layer (scans) and images. always: every PDF page, for PDFs with a broken text layer. never: no OCR (images then fail, free).

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

Languages of the text for OCR, 1 to 4 codes (Tesseract or ISO): eng, deu, fra, spa, ita, por, nld, pol, rus, ukr, tur, jpn, chi_sim, chi_tra, kor, ara, hin, vie, ind, swe, dan, nor, fin, ces (or en, de, ja, zh ...). Default: eng.

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

Pages (or slides) to convert, for example "1-5, 8, 12-". Empty (default): all pages, up to maxPagesPerFile.

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

Stop after this many pages or slides of one file (the row is then marked truncated). Default 200.

## `extractTables` (type: `boolean`):

Detect tables on PDF text pages and return them as Markdown tables (Word, PowerPoint, Excel, HTML and CSV tables are always kept). Default true.

## `includePages` (type: `boolean`):

Add a pages list to each row: page number, text, whether OCR was used and its confidence. Default true.

## `includePlainText` (type: `boolean`):

Add the plain text (without Markdown) to each row in the text field. Default true. The Markdown is always returned, in the row and as a .md file.

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

Resolution at which scanned PDF pages are rendered for OCR. Higher reads small print better but is slower. Default 200.

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

Larger files are skipped (free row with an error). Default 100.

## `respectRobotsTxt` (type: `boolean`):

Check each site's robots.txt before downloading. Turn off only for files you own or may download. Default true.

## Actor input object example

```json
{
  "fileUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://example.com/scan.png"
  ],
  "ocr": "auto",
  "ocrLanguages": [
    "eng",
    "deu"
  ],
  "pageRange": "1-10",
  "maxPagesPerFile": 200,
  "extractTables": true,
  "includePages": true,
  "includePlainText": true,
  "ocrDpi": 200,
  "maxFileSizeMb": 100,
  "respectRobotsTxt": true
}
```

# Actor output Schema

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

No description

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

No description

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

No description

# 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("webstack-labs/document-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("webstack-labs/document-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 '{}' |
apify call webstack-labs/document-to-markdown --silent --output-dataset

```

## MCP server setup

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