# PDF & Document to Markdown: OCR, Tables, Word, Excel (`yodeling_november/document-to-markdown`) Actor

📄 Turn PDFs (also scanned), Word, PowerPoint, Excel, CSV, HTML, EPUB and images into clean Markdown for LLMs and RAG: headings, lists and real tables, OCR in 27 languages, page-aware chunks and tables as data. Pay per page, no subscription.

- **URL**: https://apify.com/yodeling_november/document-to-markdown.md
- **Developed by:** [João Fernandes](https://apify.com/yodeling_november) (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 $1.60 / 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.
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 & Document to Markdown: OCR, Tables, Word, Excel

**Turn any document into clean Markdown that LLMs and RAG pipelines can actually use.**

Give this Actor links to your files, or upload them, and get back structured Markdown: headings, paragraphs, lists and **real tables**, not a wall of text. It reads **PDFs** (including **scanned PDFs**, with OCR in 27 languages), **Word**, **PowerPoint**, **Excel**, **CSV**, **HTML**, **EPUB**, plain text and **images**. Choose one row per document, one row per page, or **ready-to-embed chunks** that keep their page numbers and section headings.

### ✨ Why use it

- 📑 **Tables stay tables:** ruled tables and borderless (academic, booktabs-style) tables come out as Markdown tables, and also as rows of cells you can load straight into a spreadsheet or a database.
- 🧭 **Structure, not just text:** headings from font sizes and bold section titles, bullet and numbered lists, multi-column reading order, hyphenated words joined, running headers, footers and page numbers removed.
- 🔍 **OCR only where it is needed:** pages without a usable text layer (scans, photos, broken fonts) are read with OCR automatically, and you pay the OCR price only for those pages.
- 🗂️ **Nine formats, one output:** PDF, DOCX, PPTX (with speaker notes), XLSX/XLS, CSV, HTML, EPUB, TXT/MD and PNG/JPG/TIFF/WebP images all come out in the same shape.
- 🧩 **RAG-ready chunks:** paragraph-aligned chunks of the size you choose, with overlap, `pageStart`/`pageEnd` for citations and the heading path (`["4 Policies", "4.2 Expenses"]`) for context.
- 🛡️ **Robust on bad files:** each document is converted in its own process with a timeout. A broken or huge file becomes a failed row with a clear message, is not billed, and does not stop the run.
- 💸 **Pay per page:** no subscription, and OCR from $3 per 1,000 pages.

### 🗂️ Supported files

| Type | What you get |
|---|---|
| **PDF** (text) | Headings, paragraphs, lists, tables, metadata (title, author, dates). Password-protected PDFs work with *PDF password*. |
| **PDF** (scanned) | OCR text per page, in the languages you pick. |
| **Images** | PNG, JPG, TIFF (every page of a multi-page TIFF), WebP, BMP, GIF, read with OCR. |
| **Word** `.docx` | Headings from the document's styles, bold and italic, lists, links and tables. |
| **PowerPoint** `.pptx` | One section per slide, with its title, bullets with their levels, tables and speaker notes. |
| **Excel** `.xlsx` `.xls`, **CSV** | One Markdown table per visible sheet, with dates and numbers as you see them. The CSV delimiter and encoding are detected. |
| **HTML**, **EPUB** | Clean Markdown without scripts, menus or forms. EPUB chapters in reading order. |
| **Text** `.txt` `.md` | As is, tidied. |

The type is detected from the file's content, so links without a file extension work too. Old binary `.doc` and `.ppt`, OpenDocument and RTF files are not supported yet: save them as `.docx`, `.pptx` or `.pdf`.

### 💡 Use cases

- **RAG and AI assistants:** load manuals, policies, contracts, reports and research papers into a vector database, with page numbers to cite.
- **LLM pipelines:** give a model clean Markdown instead of raw PDF text, so it sees headings and tables and uses fewer tokens.
- **Data extraction:** pull the tables out of annual reports, price lists, statistics releases and invoices into rows and columns.
- **Archives and back offices:** make scanned letters, forms and old reports searchable with OCR.
- **Automation:** convert the attachments that arrive by email or in a cloud folder, through Make, Zapier, n8n or the Apify API.

### 🚀 How to use it

1. Click **Try for free**.
2. Paste the links to your documents in *Document URLs*, or upload files.
3. Pick the *Output format*: one row per document, one row per page, or RAG chunks. For scans in another language, add it under *OCR languages*.
4. Run it and download the result as **JSON, CSV or Excel**, or read it from the **Apify API**. With *Save .md files* on, each document is also saved as a `.md` file.

### 📥 Input examples

Two PDFs and a Word file, one row per document:

```json
{ "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf", "https://www.irs.gov/pub/irs-pdf/p15.pdf", "https://example.com/policy.docx"] }
```

Chunks of about 500 tokens for a vector database, with page markers:

```json
{ "urls": ["https://example.com/handbook.pdf"], "outputFormat": "chunks", "chunkSize": 2000, "chunkOverlap": 200 }
```

Scanned Portuguese and English letters, every page read with OCR:

```json
{ "urls": ["https://example.com/scan.pdf"], "ocr": "always", "ocrLanguages": ["por", "eng"] }
```

From code, send files inline as base64:

```json
{ "base64Documents": [{ "fileName": "invoice.pdf", "data": "JVBERi0xLjcK..." }] }
```

### 📦 Output

One row per document (default):

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "fileName": "fw9.pdf",
  "fileType": "pdf",
  "status": "ok",
  "title": "Form W-9 (Rev. March 2024)",
  "pageCount": 6,
  "pagesConverted": 6,
  "ocrPages": 0,
  "tableCount": 1,
  "words": 6464,
  "markdown": "…\n\n### Line 3a\n\nCheck the appropriate box on line 3a for the U.S. federal tax classification…\n\n| IF the entity/individual on line 1 is a(n) . . . | THEN check the box for . . . |\n|---|---|\n| • Corporation | Corporation. |\n| • Partnership | Partnership. |\n…",
  "tables": [{ "page": 3, "rows": [["IF the entity/individual on line 1 is a(n) . . .", "THEN check the box for . . ."], ["• Corporation", "Corporation."]] }],
  "convertedAt": "2026-10-18T22:12:27Z"
}
```

With *RAG chunks*, each row is one chunk:

```json
{
  "url": "https://example.com/handbook.pdf",
  "fileName": "handbook.pdf",
  "chunkIndex": 12,
  "chunkCount": 87,
  "text": "## 4.2 Expenses\n\nEmployees can claim travel expenses when…",
  "pageStart": 9,
  "pageEnd": 10,
  "headings": ["4 Policies", "4.2 Expenses"],
  "characters": 1984
}
```

Other fields: `author`, `createdAt`, `modifiedAt`, `characters`, `text` (with *Plain text*), `pages` (with *Per-page Markdown*), `markdownFileUrl` (with *Save .md files*) and `message`, which says why a document failed or was cut by a limit. The dataset has three views: *Documents*, *Pages* and *RAG chunks*.

### 💰 How much does it cost?

| Event | Price |
|---|---|
| Document converted | $0.002 |
| Page converted (text layer) | $0.0003 |
| Page read with OCR | $0.003 |

Word, PowerPoint, Excel, CSV, HTML, EPUB and text files count one page for every 3,000 characters of Markdown they produce. Higher Apify plans get volume discounts.

**Examples:** a 10-page PDF costs $0.005. A 200-page report costs $0.062. A 20-page scanned contract costs $0.062. 1,000 single-page invoices cost $2.30. Files that fail are not billed. You can set a maximum cost per run in the run options: the Actor stops before a document it cannot pay for, and never bills a document it does not deliver.

### 🔍 Good to know

- **Scanned PDFs:** *OCR* on *Auto* reads only pages without usable text. Use *Always* when a PDF has a text layer that is wrong (garbled characters, a bad OCR from another tool). *Max OCR pages per document* (default 200) protects you from surprise costs on very long scans.
- **Tables:** tables with ruled lines and booktabs-style tables (horizontal rules only) are detected. Tables laid out with spaces alone, with no lines at all, come out as text. In Word and HTML tables, merged cells are repeated in each column they span.
- **Very long documents:** the Markdown field holds up to 1.5 million characters. Longer documents are saved whole as a `.md` file, linked in `markdownFileUrl`.
- **Limits:** files up to 500 MB (*Max file size*, default 100 MB) and up to 10,000 pages per document (*Max pages per document*, default 1,000).
- **Downloads:** links must be directly downloadable without a login. Files you upload are read from your own key-value store.
- **Privacy:** your files are processed only inside your own Apify run and are not kept by this Actor after the run. The results are stored in your run's dataset, under your account's data retention settings.

### ❓ FAQ

**Does it use an AI model or send my files anywhere?** No. Conversion and OCR run inside the Actor with open-source engines (pdfplumber, pdfium, Tesseract). Nothing is sent to an external AI service.

**How good is the OCR?** It is Tesseract 5 at 300 dpi, which reads clean printed text very well. Handwriting and low-quality photos give weaker results.

**Can I get one row per page?** Yes: set *Output format* to *One row per page*. Slides, sheets and EPUB chapters count as pages.

**Can I convert web pages?** You can pass an HTML link, and the Actor converts that one page. To crawl a whole website, use a crawler and send it the pages.

**Something looks wrong?** Open an issue on the Actor's Issues tab with a link to the file (or a similar one) and the input you used.

### 🔗 More from the same developer

- [European Public Data MCP Server](https://apify.com/yodeling_november/public-data-mcp): official European data as tools for your AI agent.
- [Portuguese Case Law](https://apify.com/yodeling_november/portuguese-case-law): Portuguese court decisions with full text, for legal AI.
- [EU Company Verification (KYB)](https://apify.com/yodeling_november/eu-company-verification): bulk checks in 9 official registries.
- [Public Tenders: Portugal, Spain & EU](https://apify.com/yodeling_november/public-tenders-pt-es-eu): a daily feed of new calls for tender.

# Actor input Schema

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

Direct links to the files: PDF (also scanned), Word (.docx), PowerPoint (.pptx), Excel (.xlsx, .xls), CSV, HTML, EPUB, text, or images (PNG, JPG, TIFF, WebP). The file type is detected from its content, so links without an extension work too.

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

Or upload files from your computer. They are stored in one of your key-value stores and read from there.

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

One row per document (full Markdown), one row per page, or ready-to-embed RAG chunks with page numbers and section headings.

## `includeTables` (type: `boolean`):

Also return every table as rows of cells (the first row is the header). Tables are always in the Markdown as well.

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

Also return the content as plain text, without Markdown marks.

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

With 'One row per document', also return a list with the Markdown of each page.

## `pageMarkers` (type: `boolean`):

Put an HTML comment such as  before each page in the Markdown, so you can cite page numbers.

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

Auto reads scanned pages (pages without a usable text layer) and images with OCR. Always runs OCR on every PDF page, for PDFs whose text layer is wrong. Off never runs OCR. OCR pages are billed as 'OCR page'.

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

Languages of the scanned text. Pick only the ones you need: each extra language makes OCR slower.

## `maxOcrPagesPerDocument` (type: `integer`):

Upper limit of pages read with OCR in one document. Scanned pages over the limit are left empty and reported in 'message'.

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

Only the first pages (slides, sheets, chapters) up to this number are converted.

## `pdfPassword` (type: `string`):

Password to open protected PDFs. Used for every PDF in the run.

## `chunkSize` (type: `integer`):

With 'RAG chunks': target size of each chunk. 2,000 characters is about 500 tokens. Chunks end on paragraph boundaries.

## `chunkOverlap` (type: `integer`):

With 'RAG chunks': how much of the end of a chunk is repeated at the start of the next one (whole paragraphs, up to this size).

## `includeSlideNotes` (type: `boolean`):

Include the speaker notes of each slide.

## `saveMarkdownFiles` (type: `boolean`):

Also save each document as a .md file in the run's key-value store, with its link in 'markdownFileUrl'. Very long documents are always saved this way.

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

Larger files are skipped and reported as failed rows.

## `timeoutPerDocumentSecs` (type: `integer`):

A document that takes longer is stopped and reported as a failed row (not billed).

## `base64Documents` (type: `array`):

Files sent inline, as objects {"fileName": "invoice.pdf", "data": "<base64>"} or plain base64 strings.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ],
  "outputFormat": "document",
  "includeTables": true,
  "includePlainText": false,
  "includePages": false,
  "pageMarkers": false,
  "ocr": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "maxOcrPagesPerDocument": 200,
  "maxPagesPerDocument": 1000,
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "includeSlideNotes": true,
  "saveMarkdownFiles": false,
  "maxFileSizeMb": 100,
  "timeoutPerDocumentSecs": 900
}
```

# Actor output Schema

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

Converted documents: Markdown, tables, metadata, or one row per page or chunk, depending on 'Output format'.

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

The .md files saved with 'Save .md files' (and for very long documents).

# 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://www.irs.gov/pub/irs-pdf/fw9.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yodeling_november/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 = { "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("yodeling_november/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 '{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ]
}' |
apify call yodeling_november/document-to-markdown --silent --output-dataset

```

## MCP server setup

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