# PDF & Document to Markdown for RAG - Word, PPT, Excel (`kantolabs/document-to-markdown`) Actor

Convert PDF, Word (DOCX), PowerPoint (PPTX), Excel (XLSX/XLS), EPUB, HTML, CSV and more from URLs into clean LLM-ready Markdown with page counts, metadata and heading-aware RAG chunks. $4 per 1,000 documents, any format. Failed files are free.

- **URL**: https://apify.com/kantolabs/document-to-markdown.md
- **Developed by:** [Kanto Labs](https://apify.com/kantolabs) (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 $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

## PDF & Document to Markdown - LLM-ready text and RAG chunks from any file URL

**Give it links to PDF, Word, PowerPoint, Excel, EPUB or HTML files; get back clean Markdown, page
counts, metadata and ready-to-embed chunks** for your LLM, vector database or RAG pipeline.

- **One actor for every office format** - PDF, DOCX, PPTX, XLSX, XLS, EPUB, HTML, CSV, JSON, XML, IPYNB, MSG, ZIP
- **$4 per 1,000 documents** ($0.004 each), any format, any page count up to the size limit
- **Failed files are free** - downloads that fail, unsupported formats and files with no text cost nothing
- **Heading-aware chunks** with the section path attached, so no extra splitting step is needed

### What does Document to Markdown do?

For every document URL you provide, the actor downloads the file, detects its real type from the bytes
(not just the extension), converts it to Markdown and returns one dataset row with:

- the full **Markdown** text - headings stay headings, tables become Markdown tables, slides are numbered,
  PDF pages are marked with `<!-- Page N -->` so answers can cite a page
- **page count** (PDF) or slide count (PowerPoint), **word and character counts**
- **file metadata**: file name, format, size, content type, SHA-256 hash (for de-duplication)
- optional **RAG chunks** of the size you choose, each with its `headingPath` (e.g. `["Methods", "Data"]`)
- optional downloadable **.md file** saved to the run's key-value store

### Use cases

- **RAG and AI agents** - turn reports, manuals, papers and policies into chunks for Pinecone, Qdrant, Weaviate, pgvector or Chroma.
- **LLM prompts** - put a whole contract, deck or spreadsheet into a prompt as readable Markdown.
- **Knowledge bases** - convert a folder of mixed office files into one searchable Markdown corpus.
- **Research** - batch-extract text from arXiv papers and other academic PDFs.
- **Data pipelines** - replace a PDF tool, a Word tool and an Excel tool with a single step.
- **Change detection** - the `sha256` field tells you when a published document actually changed.

### How to use it

1. Click **Try for free**.
2. Paste direct links to your documents into **Document URLs**, one per line.
3. Optionally set **RAG chunk size** (for example 1500 characters) and turn on **Save .md files**.
4. Click **Start**, then download the results as JSON, CSV or Excel, or read them through the API.

#### Input example

```json
{
    "documentUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://example.com/files/annual-report.docx"
    ],
    "chunkSize": 1500,
    "chunkOverlap": 150,
    "saveMarkdownFiles": true
}
```

`documentUrls` accepts plain strings **or** Apify-style objects - `[{"url": "https://..."}]` works too,
so you can pass the output of another actor or a request list directly.

| Field | What it does | Default |
|---|---|---|
| `documentUrls` | Direct links to documents, one per line (strings or `{"url": ...}` objects) | one example PDF |
| `chunkSize` | Characters per RAG chunk; `0` = no chunking | `0` |
| `chunkOverlap` | Characters repeated between consecutive chunks | `150` |
| `includeMarkdown` | Put the full Markdown into each result | `true` |
| `saveMarkdownFiles` | Also save a downloadable `.md` file per document | `false` |
| `maxFileSizeMb` | Skip larger files (not charged) | `50` |
| `maxConcurrency` | Documents processed in parallel | `3` |
| `downloadTimeoutSecs` / `conversionTimeoutSecs` | Per-file time limits | `120` / `300` |

Local files: upload them anywhere that gives a direct link (an Apify key-value store record, S3, a
pre-signed URL, a public share link) and pass that link.

### Output example

One dataset item per document. Real output from a cloud run of this actor on the "Attention Is All
You Need" paper (15-page PDF, downloaded and converted in 277 ms; `markdown` shortened, `chunks` from a test run with
`chunkSize: 1500`):

```json
{
    "url": "https://arxiv.org/pdf/1706.03762",
    "finalUrl": "https://arxiv.org/pdf/1706.03762",
    "fileName": "1706.03762v7.pdf",
    "format": "PDF",
    "extension": ".pdf",
    "contentType": "application/pdf",
    "fileSizeBytes": 2215244,
    "sha256": "bdfaa68d8984f0dc02beaca527b76f207d99b666d31d1da728ee0728182df697",
    "title": null,
    "pageCount": 15,
    "charCount": 39846,
    "wordCount": 6177,
    "markdown": "<!-- Page 1 -->\nProvided proper attribution is provided, Google hereby grants permission to\nreproduce the tables and figures in this paper ...\nAttention Is All You Need\nAshish Vaswani∗\nGoogle Brain ...",
    "chunks": [
        { "index": 0, "text": "<!-- Page 1 -->\nProvided proper attribution ...", "headingPath": [], "charCount": 1475 },
        { "index": 1, "text": "by over 2 BLEU. On the WMT 2014 English-to-French translation task, ...", "headingPath": [], "charCount": 1546 }
    ],
    "markdownFileKey": null,
    "processingTimeMs": 277,
    "error": null
}
```

For Word, PowerPoint and EPUB files the Markdown keeps real headings (`#`, `##`) and each chunk's
`headingPath` shows the section it came from - useful as metadata in your vector store. In our test
set a 6-slide PPTX, a Word paper, an Excel workbook (each sheet becomes a Markdown table), an EPUB and
an HTML page all converted; a YouTube link and a PNG image were rejected with a clear error and not
charged.

### Supported formats

| Format | Extensions |
|---|---|
| PDF (with a text layer) | `.pdf` |
| Word | `.docx` |
| PowerPoint | `.pptx` |
| Excel | `.xlsx`, `.xls` |
| E-books | `.epub` |
| Web pages | `.html`, `.htm` |
| Data | `.csv`, `.json`, `.xml`, `.ipynb` (Jupyter) |
| Feeds | RSS, Atom |
| Email | `.msg` (Outlook) |
| Text | `.txt`, `.md` |
| Archives | `.zip` containing any of the above |

### How much does it cost?

**$4 per 1,000 converted documents** ($0.004 each), pay-per-event, the same price for a 1-page memo
and a 300-page manual. Apify platform compute is included. There is also a one-off start fee of
$0.00005 per GB of run memory (the default 2 GB run = $0.0001 per run).

| You convert | You pay |
|---|---|
| 1 document | $0.004 |
| 50 PDFs | $0.20 |
| 1,000 documents | $4.00 |
| 20,000 documents | $80.00 |

**What is free:** any document that fails - download error, file too large, unsupported type, a
scanned PDF with no text layer, a conversion error or timeout. Those rows come back with an `error`
message and cost nothing. Only documents that produced Markdown are charged.

**Cost cap:** set *Maximum cost per run* in the run options; the actor stops cleanly when it is
reached. Apify's free plan includes monthly platform credit you can use to try it.

### How it compares

| | This actor | PDF-only extractors | Hosted document-AI / OCR services |
|---|---|---|---|
| Formats | PDF + Word, PowerPoint, Excel, EPUB, HTML, CSV and more | PDF | Varies |
| Price model | Flat $0.004 per document, any page count | Varies (per file, per page) | Usually per page |
| Scanned (image-only) PDFs | Not supported (no OCR) - returned free | Some include OCR | Yes |
| RAG chunks with heading path | Built in | Varies | Varies |
| Runs inside your Apify account, results in your storage | Yes | Yes | No |

Pick this actor for **mixed office files and digital PDFs at a flat per-document price**. If most of
your files are scans or photos, use a tool with OCR.

### Integrations and API

- **API**: `POST https://api.apify.com/v2/acts/kantolabs~document-to-markdown/run-sync-get-dataset-items?token=YOUR_TOKEN`
  with the input JSON as the body returns the converted documents in one call.
- **LangChain / LlamaIndex**: load the dataset with the Apify dataset loaders; use `chunks[].text` as the page content and `headingPath` as metadata.
- **Vector databases**: Apify's Pinecone, Qdrant and other integrations can take the dataset directly.
- **AI agents (MCP)**: callable from Claude, ChatGPT, Cursor or any MCP client through the Apify MCP server.
- **No-code**: Make, Zapier, n8n, webhooks and schedules.

### FAQ

**Does it do OCR on scanned PDFs?**
No. Digitally created PDFs (almost all reports, papers and exports) have a text layer and work. A
pure image scan has no text; it is returned with an error and **is not charged**.

**Do I pay for files that fail?**
No. Only successfully converted documents are charged. Every failure is listed with its reason so you
can see what happened.

**Can I pass `{"url": "..."}` objects?**
Yes. `documentUrls` accepts strings, objects with a `url` field, or a mix. Anything else is reported in
the log with a clear message.

**How are PDFs handled?**
With PDFium (the PDF engine inside Chrome, via pypdfium2), which keeps word spacing intact and marks
each page. Other formats use Microsoft's open-source MarkItDown converter.

**Links without a file extension?**
They work - the type is detected from the downloaded bytes, and servers that send the wrong
`Content-Type` are handled.

**Is my data stored?**
Only in your own Apify run storage (dataset and key-value store), which you control and can delete.

**Something converted badly?**
Open an issue on the **Issues** tab with the file URL.

# Actor input Schema

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

Direct links to documents, one per line. Supported: PDF, DOCX, PPTX, XLSX, XLS, CSV, EPUB, HTML, IPYNB, MSG, TXT, MD, JSON, XML, RSS/Atom and ZIP archives of these. The type is detected from the file itself, so links without an extension work too. Via the API you can also pass objects: \[{"url": "https://..."}].

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

Split each document into heading-aware chunks of about this many characters for embeddings / vector databases. 0 turns chunking off.

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

How many characters from the end of the previous chunk are repeated at the start of the next one.

## `includeMarkdown` (type: `boolean`):

Put the whole Markdown text in each result. Turn off if you only need the chunks or the saved .md files.

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

Also store each converted document as a downloadable .md file. The key is returned in `markdownFileKey`.

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

Larger files are skipped (and not charged).

## `maxConcurrency` (type: `integer`):

How many documents are downloaded and converted in parallel.

## `downloadTimeoutSecs` (type: `integer`):

Give up downloading a file after this many seconds.

## `conversionTimeoutSecs` (type: `integer`):

Give up converting a single file after this many seconds.

## Actor input object example

```json
{
  "documentUrls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "chunkSize": 0,
  "chunkOverlap": 150,
  "includeMarkdown": true,
  "saveMarkdownFiles": false,
  "maxFileSizeMb": 50,
  "maxConcurrency": 3,
  "downloadTimeoutSecs": 120,
  "conversionTimeoutSecs": 300
}
```

# Actor output Schema

## `results` (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 = {
    "documentUrls": [
        "https://arxiv.org/pdf/1706.03762"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kantolabs/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 = { "documentUrls": ["https://arxiv.org/pdf/1706.03762"] }

# Run the Actor and wait for it to finish
run = client.actor("kantolabs/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 '{
  "documentUrls": [
    "https://arxiv.org/pdf/1706.03762"
  ]
}' |
apify call kantolabs/document-to-markdown --silent --output-dataset

```

## MCP server setup

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