# PDF to Markdown: DOCX, PPTX, XLSX, OCR & RAG Chunks (`sunburst077/document-to-markdown-rag`) Actor

Convert PDF, DOCX, PPTX, XLSX, and HTML to clean Markdown that preserves tables and headings. Optional Tesseract OCR for scanned pages and heading-aware RAG chunks with source, page, and section metadata.

- **URL**: https://apify.com/sunburst077/document-to-markdown-rag.md
- **Developed by:** [Sunburst](https://apify.com/sunburst077) (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 $5.00 / 1,000 document processeds

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 to Markdown: DOCX, PPTX, XLSX, OCR & RAG Chunks

Convert PDF, DOCX, PPTX, XLSX and HTML files into clean Markdown and heading-aware RAG chunks, with optional OCR for scanned PDFs. The output is GitHub-flavored Markdown with headings, pipe tables and page markers kept, and each chunk is tagged with source, page and section, ready for an embedding model or vector store. OCR uses Tesseract (English only) and is off by default. Conversion runs inside the Actor on open-source libraries, with no paid parsing APIs. You pay per successful document, and failed documents are free.

### Features

- **Formats:** PDF, DOCX, PPTX, XLSX, HTML, plus PNG/JPEG/TIFF/WebP when OCR is on
- **Sources:** URL list, Console file upload, or an existing Apify key-value store
- **Tables and headings** as `|` pipe tables and ATX `#` headings
- **Page markers** in the Markdown (`<!-- page:3 -->`) so you can cite a page
- **Quality modes**
  - `auto` (default) — MarkItDown for office/HTML, PyMuPDF for born-digital PDFs
  - `fast` — the same Fast converters
  - `accurate` — Docling layout and table reconstruction **if Docling is installed in the image**. This Store build does not include Docling, so Accurate **falls back to Fast** and still returns Markdown
- **OCR** off by default. English only (`eng`)
- **RAG chunking** — configurable size / overlap, heading-aware, tables kept whole
- **Per-document errors** — one corrupt file does not fail the run
- **Size, page, and timeout caps** so a huge upload cannot blow the run

#### What’s new in 1.1.0

Titles come from PDF metadata or the first real heading, never a page marker like `<!-- page:1 -->`. PDF tables keep multi-level headers and colspans (no more `BL|EU` splits). Math decimals such as `0 _._ 1` are cleaned to `0.1`.

### Input

Provide **at least one** of: document URLs, uploaded files, or a key-value store.

| Field | Default | What it does |
| --- | --- | --- |
| `startUrls` | *(prefilled example PDF)* | HTTP(S) links to documents or HTML pages |
| `files` | — | Console file upload (PDF / Office / HTML) |
| `keyValueStoreId` + `keyValueStoreKeys` | — | Read records from an existing store. Empty keys = every supported record |
| `quality` | `auto` | `auto` / `fast` / `accurate` (Accurate falls back to Fast on this build) |
| `enableOcr` | `false` | Tesseract on pages with no text layer |
| `ocrLanguage` | `eng` | English only on this build |
| `enableChunking` | `true` | Build RAG chunks |
| `chunkSize` | `1000` | Target characters per chunk |
| `chunkOverlap` | `150` | Overlap between windows |
| `headingAware` | `true` | Split on heading changes; store heading path as `section` |
| `pushChunksAsItems` | `false` | Also emit one dataset row per chunk |
| `saveMarkdownToKvs` | `true` | Write `document-*.md` files to the default key-value store |
| `maxFileSizeMb` | `50` | Skip oversized files |
| `maxPages` | `100` | PDF pages / PPTX slides / XLSX sheets |
| `timeoutSeconds` | `300` | Per-document timeout |
| `maxDocuments` | `50` | Cap sources in input order |

#### Example input

```json
{
    "startUrls": [
        { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" },
        { "url": "https://example.com" }
    ],
    "quality": "auto",
    "enableOcr": false,
    "enableChunking": true,
    "chunkSize": 1000,
    "chunkOverlap": 150
}
```

### Output

Each document becomes one **dataset** item. Failed files become `status: "error"` rows with an `error` message; the run continues.

```json
{
    "type": "document",
    "status": "success",
    "source": "https://example.com/report.pdf",
    "sourceType": "url",
    "format": "pdf",
    "title": "Annual Report",
    "markdown": "<!-- page:1 -->\n\n# Annual Report\n\n| Metric | 2025 |\n| --- | --- |\n| Revenue | 14 |\n",
    "markdownKey": "document-report-1.md",
    "pageCount": 12,
    "ocrPages": 0,
    "converter": "pymupdf4llm",
    "chunkCount": 8,
    "chunks": [
        {
            "text": "# Annual Report\n\n...",
            "source": "https://example.com/report.pdf",
            "page": 1,
            "section": "Annual Report > Financials",
            "chunkIndex": 0,
            "startChar": 0,
            "endChar": 980
        }
    ]
}
```

When `saveMarkdownToKvs` is on, the same Markdown is also a downloadable key-value store record.

### Pricing (pay-per-event)

Users pay **only** for these events — not extra platform usage on top.

| Event | When it fires | Price |
| --- | --- | --- |
| `document-processed` | Once per **successful** document (up to 10 pages) | **$0.005** |
| `extra-page` | Each page after the first **10** | **$0.001** |
| `ocr-page` | Each page that actually ran OCR | **$0.008** |

Failed documents are free.

**Examples:**

- 8-page DOCX, no OCR → $0.005
- 24-page PDF, no OCR → $0.005 + 14 × $0.001 = **$0.019**
- 12-page scanned PDF, all pages OCR’d → $0.005 + 2 × $0.001 + 12 × $0.008 = **$0.103**

Default run memory is **2048 MB**. For OCR on many pages, raise the run to **4096 MB**.

### How it works

1. Download each source (URL, upload, or key-value store) with a streaming size cap.
2. Detect format from extension / `Content-Type`.
3. Convert:
   - Office & HTML → MarkItDown (HTML first goes through readability)
   - Born-digital PDF → PyMuPDF (Markdown tables, page markers)
   - Scanned PDF / image → Tesseract OCR when `enableOcr` is on (English)
   - `quality=accurate` → Docling when present, otherwise the Fast path above
4. Optionally chunk on headings, keeping pipe tables atomic.
5. Charge the events above, push the dataset row, save the `.md` file.

### Limits

- Default 50 MB / 100 pages / 180 s / 50 documents per run — raise in input if you need more
- `.doc` / `.ppt` / `.xls` (legacy binary) are best-effort; prefer OOXML
- OCR is **English only**
- Accurate/Docling is not in this Store image; the Fast converter is used instead
- This Actor does not call Unstructured, Azure Document Intelligence, or any other paid parser

### More actors from this developer

- [Google Trends Scraper & API: Interest, Related, Trending](https://apify.com/sunburst077/google-trends-reliable): interest over time, by region, and related queries from Google Trends.
- [Google News Scraper: Full-Text Articles to Markdown](https://apify.com/sunburst077/google-news-to-markdown): Google News articles as Markdown and RAG chunks for any topic or company.
- [Greenhouse, Lever, Ashby & Workable Jobs API](https://apify.com/sunburst077/multi-ats-jobs-feed): one clean jobs feed from company job boards, with salaries when posted.
- [Website Screenshot & URL to PDF API (Bulk, Full Page)](https://apify.com/sunburst077/website-screenshot-pdf): full-page and mobile screenshots (PNG, JPEG, WebP) and PDFs of any URL.
- [Wayback Machine API: Snapshots & Archived Pages](https://apify.com/sunburst077/wayback-machine-snapshots): every archived capture of a URL or domain, the snapshot closest to a date, and archived pages as Markdown.

### License

MIT. Third-party libraries keep their own licenses (MIT MarkItDown, Apache-2.0 PyMuPDF, Apache-2.0 Tesseract).

# Actor input Schema

## `startUrls` (type: `array`):

HTTP(S) links to PDF, DOCX, PPTX, XLSX, or HTML documents. HTML pages are cleaned with a readability pass before Markdown conversion. One bad URL does not fail the run.

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

Upload PDF, Word, PowerPoint, Excel, or HTML files. The Console stores each file in a key-value store and passes the record URL to the Actor.

## `keyValueStoreId` (type: `string`):

Read documents already stored in an Apify key-value store. Combine with keys below, or leave keys empty to convert every supported record.

## `keyValueStoreKeys` (type: `array`):

Specific record keys to convert (for example report.pdf). Leave empty to convert every PDF/DOCX/PPTX/XLSX/HTML record in the store.

## `quality` (type: `string`):

Auto (recommended) and Fast use MarkItDown for office/HTML and PyMuPDF for born-digital PDFs. Accurate uses Docling layout + table reconstruction when Docling is installed in the image; this Store build does not include Docling, so Accurate falls back to Fast and still returns Markdown.

## `saveMarkdownToKvs` (type: `boolean`):

Write each successful conversion as a .md record (document-<name>-<n>.md) in the default key-value store, in addition to the dataset row.

## `enableOcr` (type: `boolean`):

Run Tesseract on PDF pages (or images) that have no usable text layer. Leave off for born-digital PDFs — it is the slowest and most expensive option. Uses the system Tesseract binary, not a cloud OCR API.

## `ocrLanguage` (type: `string`):

Tesseract language. This build ships English only (eng). Other codes are not available.

## `enableChunking` (type: `boolean`):

Split the Markdown into overlapping chunks for embeddings / vector stores. Each chunk keeps source, page, and section (heading path) metadata.

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

Target characters per chunk. 800–1500 works well for most embedding models. Tables larger than this stay in one chunk so rows are not split.

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

Characters copied from the end of the previous chunk onto the next one so sentences are not cut off at the boundary.

## `headingAware` (type: `boolean`):

Start a new chunk when the heading path changes (for example ## Revenue → ### Q3) and attach the heading path as section metadata.

## `pushChunksAsItems` (type: `boolean`):

Useful for Zapier / Make / RAG loaders that expect one dataset item per chunk. The parent document row is still written.

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

Skip (do not fail the run) any file larger than this. Keeps memory and download time predictable.

## `maxPages` (type: `integer`):

Only the first N PDF pages (or PPTX slides / XLSX sheets) are converted. Extra pages are billed after the first 10 on successful documents.

## `timeoutSeconds` (type: `integer`):

Give up on a single document after this many seconds and move on.

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

Hard cap on how many sources this run will process, in input order.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    },
    {
      "url": "https://example.com"
    }
  ],
  "quality": "auto",
  "saveMarkdownToKvs": true,
  "enableOcr": false,
  "ocrLanguage": "eng",
  "enableChunking": true,
  "chunkSize": 1000,
  "chunkOverlap": 150,
  "headingAware": true,
  "pushChunksAsItems": false,
  "maxFileSizeMb": 50,
  "maxPages": 100,
  "timeoutSeconds": 300,
  "maxDocuments": 50
}
```

# Actor output Schema

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

Dataset items: one row per document (and per chunk when pushChunksAsItems is on).

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

Downloadable .md records written by the Actor.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
        },
        {
            "url": "https://example.com"
        }
    ],
    "ocrLanguage": "eng"
};

// Run the Actor and wait for it to finish
const run = await client.actor("sunburst077/document-to-markdown-rag").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 = {
    "startUrls": [
        { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" },
        { "url": "https://example.com" },
    ],
    "ocrLanguage": "eng",
}

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

```

## MCP server setup

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

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/w00lpGNjW4WnbmUm4/builds/bRgAub28VAFAAqgch/openapi.json
