Documents and media
PDF to Markdown for RAG: Headings, Tables, Chunks and OCR
Retrieval pipelines work best on text that keeps a document's structure: headings at the right level, tables as tables, and page numbers you can cite. This Actor turns document URLs into that kind of Markdown and can split it into heading-aware chunks ready to embed.
PDF to Markdown for RAG — DOCX, OCR & Chunks. Pay per result; see the Store page for current pricing.
The source and its limits
The input is any public document URL: PDF, DOCX, HTML, Markdown or plain text, plus kvs://<storeId>/<key> references to files in your own Apify key-value stores and data: URLs. Conversion runs inside the Actor with open-source libraries (pdf.js, mammoth, turndown and tesseract.js). There is no browser and no external conversion service; the Actor contacts only the URLs you give it and your optional webhook.
For PDFs it rebuilds the layout from the file's own text layer: headings from font-size and bold clusters, tables from column alignment, two-column pages read column by column, and running headers, footers and page numbers stripped. Each document becomes one record with the Markdown and its metadata (title, author, producer, creation date, page count, headings, tables and links).
Chunks and OCR
chunk: trueadds onechunkrecord per heading-aware chunk, withstartPage,endPage, theheadingPathit sits under and a token estimate. Chunk size and overlap are configurable.ocr: trueruns the bundled English OCR model on PDF pages that have no text layer. Without it, those pages are listed inneedsOcrPagesand marked in the Markdown, and nothing is recognized.
Limits worth knowing
- Files up to 25 MB by default (
maxFileSizeMb, at most 50), a 20-second download timeout by default, and 10 minutes of conversion per file. After that, the pages converted so far are returned with aPROCESSING_TIMEOUTwarning. - Layout reconstruction is heuristic. Complex forms, rotated text, equations and multi-line table cells are best effort, and images are not extracted.
- OCR is English only and runs at about two to five seconds per page.
- Password-protected, corrupt or unsupported files return an error item with a clear code instead of failing the run.
Fields you get
| Field | What it holds |
|---|---|
recordType | document, or chunk when chunking is on |
url, filename, mimeType, bytes | The source file |
pages, pagesProcessed | Page count and pages converted |
title, author, producer, creationDate | Document metadata, where the file has it |
markdown | The converted text, with <!-- page N --> markers unless pageBreaks is off |
characters, words, headings, tables, links | Size and structure of the result |
needsOcrPages, ocrPages, warnings | Pages without a text layer, pages recognized by OCR, and conversion notes |
chunkIndex, chunkCount, startPage, endPage, headingPath, tokensEstimate | On chunk records |
kvsKey | The .md file key when saveToKeyValueStore is on |
Example output
One chunk record from the "Attention Is All You Need" paper on arXiv (from the Actor's README; Markdown shortened):
{
"recordType": "chunk",
"url": "https://arxiv.org/pdf/1706.03762v7",
"filename": "1706.03762v7.pdf",
"chunkIndex": 4,
"chunkCount": 17,
"startPage": 3,
"endPage": 4,
"headingPath": [
"Attention Is All You Need",
"3 Model Architecture",
"3.2 Attention"
],
"markdown": "### 3.2 Attention\n\nAn attention function can be described as mapping a query and a set of key-value pairs to an output ...",
"tokensEstimate": 572,
"characters": 2287,
"fetchedAt": "2026-10-16T13:20:00.000Z"
}
Use cases
RAG ingestion
Turn on chunk and send the chunk records to your embedding step. startPage, endPage and headingPath let answers cite where a passage came from.
Mixed document folders
Send PDFs, DOCX files and HTML pages in one run and get the same record shape back for each. saveToKeyValueStore also writes each one as a .md file.
Scanned PDFs
Turn on ocr for scans. The Markdown marks which pages were recognized, so you can review them separately.
How to run it
You need an Apify account and its API token (Console → Settings → API & Integrations). Each run is billed per result to your account; set a maximum total charge for the run to cap spend.
With curl
The sync endpoint starts a run, waits up to 300 seconds and returns the dataset items. One arXiv paper as Markdown plus 800-token chunks with an 80-token overlap:
export APIFY_TOKEN=<your Apify API token>
curl -sS -X POST \
"https://api.apify.com/v2/acts/owlabee~pdf-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer ${APIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://arxiv.org/pdf/1706.03762v7"
],
"chunk": true,
"chunkTokens": 800,
"chunkOverlapTokens": 80,
"ocr": false,
"saveToKeyValueStore": false,
"pageBreaks": true,
"maxFileSizeMb": 25,
"downloadTimeoutSecs": 20
}'
From an AI agent (MCP)
Apify hosts an MCP server. This config exposes only this Actor to the agent:
{
"mcpServers": {
"owlabee-pdf-to-markdown": {
"url": "https://mcp.apify.com/?tools=owlabee/pdf-to-markdown",
"headers": {
"Authorization": "Bearer ${APIFY_TOKEN}"
}
}
}
}
Then ask, for example: Convert this PDF to Markdown and split it into chunks of about 800 tokens, keeping the page numbers and section headings for each chunk.
Python, Node.js and more examples
Runnable curl.sh, python.py, node.mjs and mcp.json files for this Actor are in examples/pdf-to-markdown on GitHub.
FAQ
Does it send my documents to another service?
No. Files are downloaded over HTTPS and converted inside the Actor. Only the URLs you give and your optional webhook are contacted.
How do chunks reference pages?
Each chunk carries startPage and endPage from the page markers, and headingPath, the headings it sits under.
What happens with a scanned PDF?
Pages with no text layer are listed in needsOcrPages and marked in the Markdown. Turn on ocr to have them recognized; they then appear in ocrPages.
How are DOCX and HTML pages counted?
DOCX files use their saved page count. When that is missing, and for HTML, Markdown and text, pages are estimated at one per roughly 3,000 characters (pagesEstimated: true).
I need specific fields, not Markdown.
Use the document to JSON extractor, which runs the same layout engine against a schema you define.