Document to Markdown: PDF, DOCX & HTML for RAG
Pricing
from $1.00 / 1,000 pages
Document to Markdown: PDF, DOCX & HTML for RAG
Convert PDF, Word (DOCX), HTML pages, CSV and text into clean Markdown + JSON for LLMs and RAG: headings, tables, links, metadata and ready-to-embed chunks with heading paths and page numbers. URLs, Drive/Dropbox links or base64. No OCR, no AI cost.
Pricing
from $1.00 / 1,000 pages
Rating
0.0
(0)
Developer
Dodi Abak
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Turn PDFs, Word documents, web pages, CSV and text files into clean Markdown and structured JSON that LLMs, AI agents and RAG pipelines can use right away. You get headings, paragraphs, tables (as Markdown and as JSON rows), links, metadata, word and token counts, and optional ready-to-embed chunks with section paths and page numbers.
It's fast and predictable: no OCR, no LLM calls, no browser. A 300-page PDF converts in about 5 seconds. You pay $0.001 per page.
What does it do?
| Input | What you get |
|---|---|
| PDF (URL, Drive/Dropbox link or base64) | Markdown with # headings detected from font size and boldness, paragraphs re-flowed across line breaks, hyphenated words re-joined, bullet and numbered lists, tables rebuilt from column-aligned text, <!-- page N --> markers, title/author/dates, and a needsOcr flag for scanned PDFs. Password-protected PDFs work if you pass password. |
| Word DOCX | Headings, bold/italic, bullet and numbered lists, tables, links, plus title and author from the document properties. Google Docs links are exported as DOCX automatically. |
| HTML / web page | Main content only (Mozilla Readability, the engine behind Firefox Reader View): navigation, footers, cookie banners and ads removed. GFM tables, code blocks, lists, absolute links and images. Page metadata: title, author, description, publish date, language, canonical URL. |
| CSV / TSV | A Markdown table plus JSON rows and row/column counts. The delimiter is detected automatically. |
| Markdown / plain text | Normalized and chunked. |
Every document also gets a heading outline, stats (pages, characters, words, approximate tokens, tables) and clear warnings (scanned pages, truncated files, skipped rotated text).
Why use it?
- Better RAG answers. Chunks follow the document structure: they break at headings and paragraphs, never in the middle of a table or code block, and each chunk carries its
headingPath(for example["Annual Report", "Risk Factors"]) andpageso answers can cite their source. - Made for AI agents. One call turns a link into text the model can read. Output fields are stable and documented, and failures come back as error codes (
HTTP_ERROR,PASSWORD_REQUIRED,FILE_TOO_LARGE,UNSUPPORTED_TYPE…) instead of crashing the run. - Cheap and fast. Text extraction runs locally with pdf.js and mammoth, so there's no GPU, OCR or AI bill.
- Bulk. Up to 1,000 documents per run, 3 in parallel by default.
How to use it
- Add your documents to Documents. Each one is a URL, or an object with
base64+fileName,html,textormarkdown. - Choose an Output mode: One item per document for Markdown files, or One item per chunk to load straight into a vector database (Pinecone, Qdrant, Weaviate, pgvector, Chroma…).
- Click Start. Download the results as JSON, CSV or Excel, use the API, or turn on Save .md files to get a download link for each Markdown file.
Input example
{"sources": [{ "url": "https://arxiv.org/pdf/1706.03762v7" },{ "url": "https://drive.google.com/file/d/FILE_ID/view?usp=sharing", "id": "contract-17" },{ "base64": "UEsDBBQABgAIAAAAIQ…", "fileName": "policy.docx" },{ "url": "https://example.com/locked.pdf", "password": "1234" },{ "html": "<h1>Price list</h1><table>…</table>" }],"outputMode": "document","includeChunks": true,"chunkSize": 2000,"chunkOverlap": 200}
Output example (document mode, shortened)
{"sourceIndex": 0,"status": "ok","url": "https://arxiv.org/pdf/1706.03762v7","fileName": "1706.03762v7.pdf","type": "pdf","title": "Attention Is All You Need","markdown": "<!-- page 1 -->\n\n# Attention Is All You Need\n\n…\n\n### Abstract\n\nThe dominant sequence transduction models…","headings": [{ "level": 1, "text": "Attention Is All You Need" }, { "level": 3, "text": "1 Introduction" }],"tables": [{ "page": 6, "method": "heuristic", "rows": [["Self-Attention", "O(n2 · d)", "O(1)", "O(1)"], ["Recurrent", "O(n · d2)", "O(n)", "O(n)"]] }],"links": [{ "text": "https://github.com/tensorflow/tensor2tensor", "url": "https://github.com/tensorflow/tensor2tensor" }],"metadata": { "title": null, "author": null, "producer": "pdfTeX-1.40.25", "creationDate": "2024-04-10T21:11:43Z" },"stats": { "pages": 15, "pagesProcessed": 15, "billedPages": 15, "characters": 38203, "words": 6034, "approxTokens": 9747, "tables": 5, "headings": 15 },"needsOcr": false,"warnings": ["283 rotated/vertical text fragment(s) (e.g. margin stamps) were skipped."]}
Output example (chunks mode: one item per chunk)
{"sourceIndex": 0,"title": "Attention Is All You Need","chunkIndex": 5,"chunkCount": 27,"headingPath": ["Attention Is All You Need", "3 Model Architecture"],"page": 2,"text": "### 3 Model Architecture\n\nMost competitive neural sequence transduction models have an encoder-decoder structure…","chars": 1437,"approxTokens": 360}
A document that can't be converted returns {"status": "error", "errorCode": "HTTP_ERROR", "error": "Download failed: HTTP 404 …"} and isn't charged.
Input options
| Field | Default | Description |
|---|---|---|
sources | (required) | URLs or objects (url, base64 + fileName, html, text, markdown). Optional per source: type, password, id. |
outputMode | document | document (one item per file) or chunks (one item per chunk). |
includeChunks | false | Add a chunks array to document items. |
chunkSize / chunkOverlap | 2000 / 200 | In characters (about 4 per token). |
mainContentOnly | true | Web pages: keep only the main article. |
linkStyle | inline | text drops URLs from the Markdown to save tokens (they stay in links). |
detectTables | true | PDF table reconstruction. |
pageMarkers | true | <!-- page N --> between PDF pages. |
maxPages | 300 | PDF pages converted (and billed) per document. |
includeTables / includeLinks / includeText | true / true / false | Extra JSON fields. |
saveMarkdownFiles | false | Save doc-0001.md… to the key-value store and return markdownUrl. |
maxFileSizeMb | 25 | Download size limit (up to 100). |
requestTimeoutSecs | 60 | Download timeout per URL. |
concurrency | 3 | Documents processed in parallel. |
Pricing
$0.001 per page ($1 per 1,000 pages). There's no subscription and no charge for failed documents.
- PDF: one page = one PDF page converted (capped by
maxPages). - DOCX, HTML, CSV, text: one page = every 3,000 characters of extracted text (minimum 1). A typical web article is 1–4 pages.
Example: 100 ten-page PDFs cost $1.00. The Actor respects your run's maximum charge and stops cleanly when it's reached.
Use with AI agents (MCP) and the API
MCP: add this Actor to Claude Desktop, Cursor, VS Code or any MCP client through the Apify MCP server:
https://mcp.apify.com?tools=dodiabak/document-to-markdown
The agent can then call it with {"sources": ["https://…/file.pdf"]} and read the markdown field.
API (synchronous, returns the items):
curl -X POST "https://api.apify.com/v2/acts/dodiabak~document-to-markdown/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{"sources": ["https://arxiv.org/pdf/1706.03762v7"], "outputMode": "chunks"}'
Python:
from apify_client import ApifyClientclient = ApifyClient("YOUR_API_TOKEN")run = client.actor("dodiabak/document-to-markdown").call(run_input={"sources": ["https://example.com/report.pdf"]})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["stats"]["pages"], item["markdown"][:200])
It also works with LangChain, LlamaIndex, n8n, Make and Zapier through the Apify integrations.
FAQ
Does it do OCR on scanned PDFs or images? No. It reads the PDF text layer, which most PDFs from Word, LaTeX, browsers and reporting tools have. Scanned PDFs return needsOcr: true with a warning so your pipeline can send them to an OCR service. Image files are rejected with UNSUPPORTED_TYPE.
How good is table extraction? Tables in DOCX and HTML are exact. PDF tables are rebuilt from column positions ("method": "heuristic"). Simple tables with one line per row work well. Tables with merged cells or wrapped multi-line cells may come out partly as plain text.
Which files are supported? PDF, DOCX, HTML, CSV/TSV, Markdown and TXT. Legacy .doc, .xls, .pptx and .xlsx aren't supported yet; save them as PDF or DOCX.
Can I use private files? Yes. Send the file as base64, or share a Google Drive, Docs or Dropbox link as "Anyone with the link". Files are processed in memory, stored only in your own run storage, and never sent to a third party.
Does it crawl websites? No. It fetches exactly the URLs you give it, one request per document. To convert a whole site, run a crawler first and pass its URLs or HTML here.
What about multi-column layouts? Text is read line by line from top to bottom, so two-column academic papers can have columns interleaved on some lines. Single-column documents (most reports, contracts, invoices, manuals) convert cleanly.
Limitations
- No OCR and no image descriptions. Formulas come out as plain text.
- PDF heading levels are inferred from font size, so very uniformly styled PDFs may have few or no headings.
- Max 1,000 documents per run and 100 MB per file.
Tech and sources
pdf.js (Mozilla, Apache-2.0), mammoth (BSD-2), Readability (Mozilla, Apache-2.0), Turndown + GFM plugin (MIT), linkedom (ISC).
Questions or a document that converts badly? Open an issue on the Actor's Issues tab and include a sample file if you can.