# PDF to Markdown – DOCX, PPTX, XLSX & HTML for LLM & RAG (`openharvest/document-to-markdown`) Actor

Convert PDF to Markdown, plus Word (DOCX), PowerPoint (PPTX), Excel (XLSX/CSV) and HTML documents, into clean LLM-ready Markdown or plain text. Keeps headings, lists and tables, adds metadata and RAG chunking (per page or by tokens with overlap). Pay only per page.

- **URL**: https://apify.com/openharvest/document-to-markdown.md
- **Developed by:** [Enes Bulanık](https://apify.com/openharvest) (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

$1.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 to Markdown – DOCX, PPTX, XLSX & HTML for LLM & RAG

Convert PDF to Markdown, plus Word (DOCX), PowerPoint (PPTX), Excel (XLSX/CSV) and HTML documents, into clean LLM-ready Markdown or plain text. Keeps headings, lists and tables, adds metadata and RAG chunking (per page or by tokens with overlap). Pay only per page.

**Price: $1 per 1,000 pages.** Failed downloads and unsupported files are free.

### What does Document to Markdown do?

Give it links (or uploaded files) and it returns one dataset item per document, or one item per chunk:

- 📄 **PDF**: text layer with **headings** (from font size, bold weight and section numbers like `3.2.1`), bullet and numbered lists, de-hyphenated paragraphs. Running headers, footers and page numbers are removed. Handles large PDFs (a 142-page PDF converts in about 5 s).
- 📝 **DOCX**: headings, lists, bold/italic, links, footnotes and **tables as Markdown tables** (via mammoth).
- 📊 **PPTX**: one section per slide (`## Slide 3: Title`), bullet hierarchy, tables, optional **speaker notes**.
- 📈 **XLSX / XLS / ODS / CSV**: every sheet becomes a Markdown table with formatted values (dates and currencies as displayed).
- 🌐 **HTML pages**: main-content extraction (Mozilla Readability) without menus, footers or ads. Links and images are made absolute.
- 🧾 **TXT / MD**: passed through and chunked.
- 🏷 **Metadata**: title, author, subject, keywords, created/modified dates, producer, language, sheet names, page count.
- ✂️ **Chunking for RAG**: `none`, `page` (page, slide or sheet; DOCX/HTML split at H1/H2 headings) or `tokens` (for example 800 tokens with 100 overlap). Every chunk has `pageStart`/`pageEnd` and `tokensEstimate`.

The file type is detected from the file content, so links without an extension (such as `https://arxiv.org/pdf/1706.03762`) work too.

### Why use it?

- **One tool for all office formats.** Other Store actors are mostly PDF-only. This one takes the mixed folders real projects have.
- **Built for LLMs and RAG.** You get Markdown structure instead of a flat text dump, chunking with overlap, token estimates, and page numbers for citations.
- **Fair pay-per-page pricing.** Chunking and output format don't change the price. You never pay for errors.
- **Fast and light.** No browser and no OCR engine, so most documents finish in under a second.

### Use cases

- **RAG / vector databases**: chunk manuals, contracts, reports and papers before embedding them into Pinecone, Qdrant, Weaviate or pgvector.
- **AI agents**: let an agent read a PDF or spreadsheet a user links to (see the MCP section).
- **Knowledge bases and search**: turn SharePoint/Drive exports (DOCX, PPTX, XLSX) into Markdown for Notion, Confluence or static sites.
- **Data extraction**: get Excel sheets and Word tables as clean Markdown tables for LLM-based extraction.
- **Research**: convert arXiv papers to Markdown for summarization or literature reviews.

### How to use it

1. Paste document links into **Document URLs**, or upload files in **Uploaded files**.
2. Choose the **Output format** (Markdown, plain text or both) and **Chunking**.
3. Optionally limit pages with **Page range** (`1-10`) and **Max pages per document**.
4. Click **Start** and export the results as JSON, CSV or Excel, or use the API.

### Input example

```json
{
    "fileUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://raw.githubusercontent.com/scanny/python-pptx/master/features/steps/test_files/test.pptx",
        "https://en.wikipedia.org/wiki/Markdown"
    ],
    "outputFormat": "markdown",
    "chunking": "tokens",
    "chunkSize": 800,
    "chunkOverlap": 100,
    "maxPages": 50
}
```

| Field | Description | Default |
|---|---|---|
| `fileUrls` | Links to PDF, DOCX, PPTX, XLSX/XLS/ODS/CSV, HTML, TXT, MD | demo arXiv PDF |
| `files` | Uploaded files (Apify key-value store) or record keys | – |
| `outputFormat` | `markdown`, `text` or `both` | `markdown` |
| `chunking` | `none`, `page` or `tokens` | `none` |
| `chunkSize` / `chunkOverlap` | Chunk size and overlap in tokens (≈ 4 characters) | 1000 / 100 |
| `pageRange` | e.g. `1-5`, `1,3,7-9`, `10-` (PDF pages, slides, sheets) | all |
| `maxPages` | Max pages per document (0 = no limit) | 200 |
| `includeTables` | Tables as Markdown tables (otherwise tab-separated) | `true` |
| `includeSpeakerNotes` | PPTX speaker notes | `true` |
| `extractMainContent` | HTML: main article only | `true` |
| `maxRowsPerSheet` | Spreadsheet row cap per sheet | 5000 |
| `maxFileSizeMb` | Skip larger files | 100 |

An empty input (`{}`) converts a demo document (the arXiv paper "Attention Is All You Need").

### Output

Real output, shortened. **Document** (`chunking: none`):

```json
{
    "type": "document",
    "url": "https://arxiv.org/pdf/1706.03762",
    "source": "url",
    "fileName": "1706.03762v7.pdf",
    "fileType": "pdf",
    "title": "Attention Is All You Need",
    "pages": 15,
    "totalPages": 15,
    "pageStart": 1,
    "pageEnd": 15,
    "chunkIndex": 0,
    "chunkCount": 1,
    "markdown": "Provided proper attribution is provided, Google hereby grants permission to reproduce the tables and figures in this paper...\n\n### Attention Is All You Need\n\n...\n\n### Abstract\n\nThe dominant sequence transduction models are based on complex recurrent or convolutional neural networks...\n\n### 1 Introduction\n\nRecurrent neural networks, long short-term memory [13] and gated recurrent [7] neural networks...\n\n#### 3.1 Encoder and Decoder Stacks\n\n...",
    "text": null,
    "tokensEstimate": 10074,
    "wordCount": 6254,
    "metadata": {
        "creator": "LaTeX with hyperref",
        "producer": "pdfTeX-1.40.25",
        "createdAt": "2024-04-10T21:11:43.000Z",
        "pdfVersion": "1.5"
    },
    "warnings": [],
    "truncated": false,
    "convertedAt": "2026-10-07T19:12:28.445Z"
}
```

**Spreadsheet** (`Book1.xlsx`, sheet 2):

```markdown
### Sheet: Sheet2

| Monitor |  | Brand |  |
| --- | --- | --- | --- |
| > 23 Inch | 19 | HP | 200 |
| 20-23 Inch | 24 | DELL | 450 |
```

**Chunk** (`chunking: tokens`): same fields, with `"type": "chunk"`, `chunkIndex`/`chunkCount` and the `pageStart`/`pageEnd` the chunk covers.

**Error (free)**:

```json
{
    "type": "error",
    "url": "https://example.com/does-not-exist.pdf",
    "fileName": "does-not-exist.pdf",
    "errorCode": "DOWNLOAD_FAILED",
    "error": "HTTP 404 Not Found"
}
```

Error codes: `INVALID_URL`, `DOWNLOAD_FAILED`, `FILE_TOO_LARGE`, `UNSUPPORTED_FORMAT`, `ENCRYPTED`, `NO_TEXT_LAYER` (scanned PDF), `NO_CONTENT`, `NO_PAGES_SELECTED`, `CONVERSION_FAILED`.

The dataset has two views in Console: **Overview** and **Document metadata**.

### Pricing

Pay per event: you pay only for converted pages, and platform usage is included.

| Event | Price | What counts as a page |
|---|---|---|
| `page` | **$0.001** ($1 per 1,000) | 1 PDF page, 1 slide, 1 sheet; DOCX: page count from document properties (or ~500 words); HTML/TXT: ~3,000 words (minimum 1) |
| Errors | **Free** | Failed downloads, unsupported or scanned files, empty documents |

| Job | Cost |
|---|---|
| 15-page paper | $0.015 |
| 100 documents × 10 pages | $1.00 |
| 142-page PDF | $0.142 |
| 50 web pages (≈1 page each) | $0.05 |

Chunking and output format do not change the price. Use **Max pages per document** and Apify's **Max cost per run** to cap spending. The Actor stops cleanly at your budget and truncates the last document if needed (`truncated: true`).

### Use with AI agents (MCP)

This Actor works as a tool for AI agents through the [Apify MCP server](https://mcp.apify.com) (Claude, Cursor, VS Code, OpenAI Agents and others). Example tool input:

```json
{ "fileUrls": ["https://arxiv.org/pdf/1706.03762"], "chunking": "page", "pageRange": "1-5", "outputFormat": "markdown" }
```

The agent gets Markdown per page with page numbers, so it can quote and cite exact pages. Use `pageRange` / `maxPages` to keep context windows and costs small.

### FAQ

**Does it do OCR on scanned PDFs?**
Not in this version. Scanned or image-only PDFs have no text layer. They are detected and returned as a free `NO_TEXT_LAYER` error row.

**How good is PDF structure detection?**
Headings are inferred from font size, bold fonts and numbering (`1`, `3.2`, `A.1`). Lists come from bullet and number prefixes. This works well for papers, reports and manuals. PDF tables are output as text lines, not Markdown tables, because PDFs have no table structure. Multi-column layouts follow the PDF's internal reading order, which is correct for most generators (LaTeX, Word, InDesign).

**Are images extracted?**
No. Images are skipped (DOCX alt text is kept as `[Image: …]`, and HTML images keep their URL).

**Old .doc / .ppt files?**
Legacy binary Word/PowerPoint files are not supported. Save them as .docx/.pptx or PDF. Legacy `.xls` spreadsheets are supported.

**Password-protected files?**
They are reported as a free `ENCRYPTED` error.

**How do I upload my own files?**
Use the **Uploaded files** field in Console. Through the API, put the files in a key-value store and pass the record URLs (or record keys of the run's default store) in `files`.

**How are tokens counted?**
`tokensEstimate` uses ~4 characters per token. That is close to OpenAI/Anthropic tokenizers for English. Use a real tokenizer if you need exact counts.

### Is it legal?

The Actor only downloads documents from the URLs you provide (or files you upload). It does not crawl or log in anywhere. Make sure you have the right to process the documents, and respect copyright and the terms of the sites hosting them. Documents can contain personal data. If yours do, handle the output in line with GDPR and other applicable laws.

### Related actors

- *Coming soon:* Academic Papers Search (OpenAlex, arXiv, Crossref)
- *Coming soon:* Website to Markdown Crawler
- *Coming soon:* Google News Scraper with full text

### Support & feedback

Found a document that converts badly? Open an issue in the **Issues** tab with the URL. We usually answer within a day.

# Actor input Schema

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

Direct links to documents: PDF, DOCX, PPTX, XLSX/XLS/ODS, CSV, HTML web pages, TXT or MD. The type is detected from the file content, so URLs without an extension work too. Example: <code>https://arxiv.org/pdf/1706.03762</code>.

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

Optional. Upload documents from your computer (they are stored in an Apify key-value store) - or enter keys of records in this run's default key-value store.

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

<b>markdown</b> keeps headings, lists, tables and links (best for LLMs). <b>text</b> is plain text without Markdown syntax. <b>both</b> returns both fields.

## `chunking` (type: `string`):

<b>none</b>: one dataset item per document. <b>page</b>: one item per PDF page / slide / sheet (DOCX and HTML are split at H1/H2 headings). <b>tokens</b>: chunks of about <i>Chunk size</i> tokens with overlap, ready for embeddings. Chunking does not change the price.

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

Target chunk size in tokens (estimated as 4 characters per token) when Chunking = tokens. Chunks break on paragraph boundaries where possible.

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

How many tokens from the end of one chunk are repeated at the start of the next (Chunking = tokens). Max half of the chunk size.

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

Optional. Convert only these pages (PDF), slides (PPTX) or sheets (XLSX), e.g. <code>1-5</code>, <code>1,3,7-9</code> or <code>10-</code>. Empty = all. Ignored for DOCX and HTML.

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

Upper limit of pages / slides / sheets converted per document (DOCX and HTML: estimated pages). You pay per page, so this also caps your cost. 0 = no limit.

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

Render tables from DOCX, PPTX, XLSX and HTML as GitHub-flavoured Markdown tables. If off, table rows are output as tab-separated lines.

## `includeSpeakerNotes` (type: `boolean`):

Append the speaker notes of each slide (PPTX) as a quote block.

## `extractMainContent` (type: `boolean`):

For web pages, keep only the main article (Mozilla Readability) and drop navigation, menus, footers and ads. Turn off to convert the whole page.

## `maxRowsPerSheet` (type: `integer`):

Spreadsheets: convert at most this many non-empty rows per sheet (protects you from giant tables).

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

Skip files larger than this (reported as a free error item).

## Actor input object example

```json
{
  "fileUrls": [
    "https://arxiv.org/pdf/1706.03762",
    "https://raw.githubusercontent.com/scanny/python-pptx/master/features/steps/test_files/test.pptx",
    "https://en.wikipedia.org/wiki/Markdown"
  ],
  "files": [
    "https://api.apify.com/v2/key-value-stores/STORE_ID/records/report.pdf"
  ],
  "outputFormat": "markdown",
  "chunking": "tokens",
  "chunkSize": 800,
  "chunkOverlap": 100,
  "pageRange": "1-10",
  "maxPages": 50,
  "includeTables": true,
  "includeSpeakerNotes": true,
  "extractMainContent": true,
  "maxRowsPerSheet": 1000,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

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

All scraped items in the default dataset (JSON, CSV, Excel and more via the dataset export).

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

Records saved in the default key-value store, e.g. the run summary.

# 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://arxiv.org/pdf/1706.03762",
        "https://raw.githubusercontent.com/mwilliamson/mammoth.js/master/test/test-data/tables.docx"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("openharvest/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 = { "fileUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://raw.githubusercontent.com/mwilliamson/mammoth.js/master/test/test-data/tables.docx",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("openharvest/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 '{
  "fileUrls": [
    "https://arxiv.org/pdf/1706.03762",
    "https://raw.githubusercontent.com/mwilliamson/mammoth.js/master/test/test-data/tables.docx"
  ]
}' |
apify call openharvest/document-to-markdown --silent --output-dataset

```

## MCP server setup

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