# PDF, Word & PowerPoint to Markdown + RAG Chunks (`oqen/document-to-markdown-chunks`) Actor

Convert PDF, DOCX and PPTX to clean Markdown with headings, lists and tables, then split into token-sized, heading-aware chunks with page numbers for RAG, vector databases and LLMs. Removes repeated headers/footers. Google Drive & Dropbox links work.

- **URL**: https://apify.com/oqen/document-to-markdown-chunks.md
- **Developed by:** [oqen](https://apify.com/oqen) (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 $2.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, Word & PowerPoint to Markdown + RAG Chunks

Turn PDFs, Word documents and PowerPoint decks into **clean Markdown** that LLMs read well, and into **chunks ready for a vector database**: token-counted, split along headings, with the heading path and page numbers attached to every chunk.

Use it to load company documents into a RAG chatbot, feed reports to GPT/Claude, build a searchable knowledge base, or migrate documents into Notion or a wiki.

### What the Markdown keeps

| | PDF | DOCX | PPTX |
|---|---|---|---|
| Headings (`#`, `##`, ...) | from font size and bold (body text size is detected per document) | from Word heading styles + Title | one `## Slide N: title` per slide |
| Tables → Markdown tables | ruled/grid tables (borderless tables stay as text in reading order) | all tables | tables, plus chart data as a table |
| Lists | bullets and numbering | bullets and numbering | bullet levels |
| Clean-up | repeated headers/footers and page numbers removed, hyphenated line breaks joined, rotated margin text and figure labels skipped, multi-column reading order | images dropped | speaker notes (optional) |

Scanned (image-only) PDF pages are detected and reported in `scannedPages`. They need OCR, which this Actor doesn't do, and they **are not charged**.

### Chunks built for retrieval

- Token-sized with the OpenAI `cl100k` tokenizer (`chunkSizeTokens`, default 500) with optional overlap
- **Heading-aware**: a new section starts a new chunk, and each chunk is prefixed with its path, e.g. `[Annual Report > 3. Risks > Currency]`
- **Tables are never cut mid-row**. Long tables are split between rows and the header row is repeated in every piece.
- `pageStart` / `pageEnd` on each chunk for citations ("see page 7")
- Stable `chunkId` (`<documentId>-0003`) so re-runs can upsert into Pinecone, Qdrant, Weaviate, pgvector...

### Input

```json
{ "documents": [{ "url": "https://arxiv.org/pdf/1706.03762" }, { "url": "https://drive.google.com/file/d/<id>/view" }],
  "chunkSizeTokens": 500, "chunkOverlapTokens": 50, "outputMode": "chunks" }
```

Google Drive, Google Docs/Slides (exported as DOCX/PPTX) and Dropbox share links are converted to direct downloads. The file must be shared as "anyone with the link".

### Output (chunk mode)

```json
{ "fileName": "report.pdf", "documentId": "3ab677dde1ab", "title": "Quarterly Report", "status": "ok",
  "chunkId": "3ab677dde1ab-0002", "chunkIndex": 2, "chunkCount": 7, "tokens": 276,
  "headingPath": "Quarterly Report 2026 > 1. Summary > 1.1 Regional breakdown", "pageStart": 1, "pageEnd": 1,
  "text": "[Quarterly Report 2026 > 1. Summary > 1.1 Regional breakdown]\n\n| Region | Q1 | Q2 | Q3 | Change % |\n|---|---|---|---|---|\n| South 0 | 8,233 | ...",
  "markdownUrl": "https://api.apify.com/v2/key-value-stores/.../records/0001-report.md" }
```

In `documents` mode you get one item per file with the whole `markdown` string. Either way, the full `.md` file and a `.meta.json` (pages, tables, scanned pages) are in the key-value store.

### Tested on

Our test corpus checks: a Word report and the same report exported to PDF (3 tables, **100% of table cells reproduced exactly** in both), a two-column arXiv paper (correct section headings, side stamp removed), the IRS W-9 form, a PPTX with nested bullets, a table and speaker notes, and a scanned PDF (correctly flagged). Bad links, HTML pages instead of files, 404s and password-protected PDFs return clear errors.

### Pricing

Pay per event: `page-converted`, one per PDF page with text, per slide, or per 500 words of DOCX/Markdown. Scanned pages, failed downloads and unsupported files are free. Use `maxPagesPerDocument` and *Max cost per run* to cap spend.

# Actor input Schema

## `documents` (type: `array`):

Links to PDF, DOCX, PPTX, Markdown or TXT files (max 100 MB). Google Drive, Google Docs/Slides and Dropbox share links are converted to direct downloads automatically (the file must be shared publicly).

## `documentsText` (type: `string`):

Whitespace- or newline-separated document URLs.

## `outputMode` (type: `string`):

Full Markdown is always also saved to the key-value store and linked as markdownUrl.

## `chunkSizeTokens` (type: `integer`):

Maximum tokens per chunk (OpenAI cl100k tokenizer). Chunks follow headings; tables are split between rows with the header repeated, never mid-row.

## `chunkOverlapTokens` (type: `integer`):

Tail of the previous chunk carried into the next one within a section, for context.

## `prependHeadingPath` (type: `boolean`):

Adds e.g. \[Annual Report > Risks > Currency] at the top of each chunk so embeddings keep their context.

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

Inserts  comments in the Markdown and fills pageStart/pageEnd on each chunk (PDF and PPTX).

## `removeHeadersFooters` (type: `boolean`):

PDF: drops text that repeats at the top/bottom of most pages (running titles, confidentiality notices, page numbers).

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

Adds slide notes as quoted text.

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

Only needed for encrypted PDFs.

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

Safety cap.

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

Longer PDFs are cut at this page (you are only charged for pages converted).

## Actor input object example

```json
{
  "documents": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    }
  ],
  "outputMode": "chunks",
  "chunkSizeTokens": 500,
  "chunkOverlapTokens": 50,
  "prependHeadingPath": true,
  "pageMarkers": true,
  "removeHeadersFooters": true,
  "includeSpeakerNotes": true,
  "maxDocuments": 100,
  "maxPagesPerDocument": 300
}
```

# Actor output Schema

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

Chunk mode: one item per chunk with text, tokens, headingPath, pageStart/pageEnd, chunkId. Document mode: one item per document with full markdown. Failed documents have status=error.

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

Full <n>-<name>.md per document plus .meta.json (title, pages, tables, scanned pages).

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

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

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

```

## MCP server setup

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

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/PpM21D0wwILvYFJX9/builds/hplSVdxE02FJevZft/openapi.json
