Document to Markdown for LLM & RAG (PDF, DOCX, PPTX, XLSX, OCR)
Pricing
Pay per event
Document to Markdown for LLM & RAG (PDF, DOCX, PPTX, XLSX, OCR)
Convert PDF, Word, PowerPoint, Excel, HTML and scanned images to clean Markdown or token-sized chunks for RAG, LangChain and LlamaIndex. Headings, tables, images and Tesseract OCR included. Pay per page.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Tidewell Software
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
an hour ago
Last modified
Categories
Security
Limited permissionsShare
Document to Markdown for LLM & RAG pipelines
Turn PDF, Word (DOCX), PowerPoint (PPTX), Excel (XLSX/XLS), HTML, EPUB, CSV and scanned images into clean, structured Markdown, or into token-sized chunks with page and heading metadata, ready for embeddings, RAG, LangChain, LlamaIndex, n8n and Make.
- Real structure. Headings come from font sizes and weights, so
#,##and###follow the document. Lists stay lists, and tables become Markdown tables. Reading order works across columns, and running headers, footers and page numbers are removed. - OCR built in. Tesseract OCR runs only on scanned pages and images, in 25 languages. Pages that already have a text layer are read directly, which is faster and exact.
- RAG-ready chunks. Split by heading, or by N tokens with overlap. Every chunk has
page_start,page_end,heading_pathand a token count. - Images. Embedded images can be saved to the key-value store and linked from the Markdown.
- Pay per page. You pay only for pages that produced text. Failed, encrypted and empty files are free.
Docs, examples, MCP setup and an honest comparison with other actors: plugin.benjaminbritton.co.uk/doc-to-markdown/
What can you use it for?
| Use case | How |
|---|---|
| RAG knowledge base from PDFs, policies, manuals and contracts | outputFormat: json, chunking: tokens, then embed chunks[].text |
| LLM-ready context for agents (Claude, GPT, Gemini) | outputFormat: markdown, then pass markdown to the model |
| Scanned archives (invoices, letters, faxes) to searchable text | Leave OCR on and set ocrLanguages |
| Slide decks and spreadsheets into a vector store | PPTX gives one page per slide, with speaker notes. XLSX gives one Markdown table per sheet |
| Convert files your crawler found | Pass a datasetId from Website Content Crawler or any scraper, plus a datasetUrlField |
| n8n / Make / Zapier automations | Call the Actor with a file URL and read the dataset row |
Supported formats
| Format | Engine | Notes |
|---|---|---|
| PDF (digital) | pdfminer.six + pdfplumber (MIT), PDFium (BSD/Apache) | Headings, lists, ruled tables, images, header/footer removal, encrypted PDFs with pdfPassword |
| PDF (scanned) | Tesseract 5 OCR (Apache-2.0) | Auto-detected per page. Rotated scans are corrected |
| DOCX | Microsoft MarkItDown (MIT) + mammoth | Headings, lists, tables, bold/italic, images |
| PPTX | MarkItDown + python-pptx | One page per slide, with tables, speaker notes and images |
| XLSX / XLS | openpyxl / xlrd | One Markdown table per sheet. Formulas without cached values are shown as =FORMULA |
| HTML | MarkItDown + BeautifulSoup | Keeps <main>/<article>, drops nav/footer/scripts and makes links absolute |
| PNG, JPEG, TIFF (multi-page), WebP, GIF, BMP | Tesseract OCR | |
| EPUB, CSV, JSON, XML, TXT, MD, IPYNB | MarkItDown |
Google Drive, Dropbox and GitHub share links are converted to direct downloads automatically.
Input example
{"fileUrls": ["https://arxiv.org/pdf/1706.03762v7","https://example.com/handbook.docx","https://example.com/scanned-invoice.png"],"outputFormat": "json","chunking": "tokens","chunkSize": 512,"chunkOverlap": 64,"ocr": true,"ocrLanguages": ["eng"],"extractTables": true,"extractImages": false}
The main options are listed below. The Input tab documents all of them.
| Field | Default | What it does |
|---|---|---|
fileUrls | File links | |
uploadedFiles | Upload files in Console | |
keyValueStoreId, keyValueStoreKeys, keyValueStorePrefix | Convert files from a key-value store | |
datasetId, datasetUrlField | url | Take URLs from a dataset |
outputFormat | markdown | markdown or json (chunks) |
chunking | none | none (one chunk per page), heading or tokens |
chunkSize, chunkOverlap | 512 / 64 | Size and overlap in cl100k_base tokens |
ocr, forceOcr, ocrLanguages, ocrDpi | on / off / eng / 300 | OCR settings |
pageRange | all | Pages to convert, e.g. 1-5,8,10-. Applies to PDF pages, slides, sheets and TIFF frames |
extractTables | on | Markdown tables from PDF ruling lines |
extractImages, minImageSize | off / 64 px | Save images to the key-value store and link them |
pageMarkers | off | Add <!-- page N --> comments for citations |
pdfPassword | Password for encrypted PDFs | |
maxFileSizeMb, documentTimeoutSecs, maxConcurrency | 100 / 300 / 3 | Limits |
Output example
One dataset row per document. The full .md file (and chunks.json in JSON mode) is always saved to the run's key-value store and linked from the row.
{"doc_id": "doc-0001","status": "succeeded","source_url": "https://arxiv.org/pdf/1706.03762v7","filename": "1706.03762v7.pdf","mime": "application/pdf","title": "Attention Is All You Need","pages": 15,"pages_processed": 15,"ocr_pages": 0,"tables_count": 0,"images": [{ "key": "doc-0001-1706.03762v7-img-001.png", "url": "https://api.apify.com/v2/key-value-stores/.../records/doc-0001-1706.03762v7-img-001.png", "page": 3, "width": 1520, "height": 2239, "mime": "image/png" }],"word_count": 6025,"token_count": 9474,"markdown_url": "https://api.apify.com/v2/key-value-stores/.../records/doc-0001-1706.03762v7.md","chunk_count": 24,"chunks": [{"chunk_id": "doc-0001#2","chunk_index": 2,"text": "## 1 Introduction\n\nRecurrent neural networks, long short-term memory [13] and gated recurrent [7] neural networks ...","token_count": 447,"page_start": 2,"page_end": 2,"heading": "1 Introduction","heading_path": ["Attention Is All You Need", "1 Introduction"],"source_url": "https://arxiv.org/pdf/1706.03762v7","filename": "1706.03762v7.pdf"}],"processing_ms": 5937,"errors": [],"warnings": []}
Failed documents get status: "failed" with an error code, such as encrypted_pdf, corrupt_file, download_failed, file_too_large, unsupported_format, no_text or timeout. They are never charged.
The dataset has four views: Overview, Markdown, Chunks (one row per chunk) and Status & errors.
Use with LangChain
from apify_client import ApifyClientfrom langchain_core.documents import Documentclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("tidewell/doc-to-markdown").call(run_input={"fileUrls": ["https://arxiv.org/pdf/1706.03762v7"],"outputFormat": "json", "chunking": "tokens", "chunkSize": 512, "chunkOverlap": 64,})docs = [Document(page_content=c["text"],metadata={"source": row["source_url"], "page": c["page_start"], "heading": c["heading"]})for row in client.dataset(run["defaultDatasetId"]).iterate_items() if row["status"] == "succeeded"for c in row["chunks"]]# -> vectorstore.add_documents(docs)
If you prefer to chunk with your own splitter (for example MarkdownHeaderTextSplitter), use outputFormat: "markdown" and read row["markdown"].
Use with LlamaIndex
from apify_client import ApifyClientfrom llama_index.core import VectorStoreIndexfrom llama_index.core.schema import TextNodeclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("tidewell/doc-to-markdown").call(run_input={"fileUrls": ["https://example.com/handbook.pdf"], "outputFormat": "json", "chunking": "heading",})nodes = [TextNode(text=c["text"], id_=c["chunk_id"],metadata={"source": row["source_url"], "page": c["page_start"], "section": " > ".join(c["heading_path"])})for row in client.dataset(run["defaultDatasetId"]).iterate_items() if row["status"] == "succeeded"for c in row["chunks"]]index = VectorStoreIndex(nodes)
Use from the API, n8n or Make
Run it synchronously and get the rows back in one HTTP call:
curl -X POST "https://api.apify.com/v2/acts/tidewell~doc-to-markdown/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"fileUrls": ["https://example.com/report.pdf"], "outputFormat": "markdown"}'
In n8n or Make, use the Apify node with the action "Run Actor and get dataset items".
Use from Claude / ChatGPT (MCP)
Run this actor from an AI assistant through Apify's hosted MCP server. This server URL loads only this actor:
https://mcp.apify.com/?tools=tidewell/doc-to-markdown
- Claude (claude.ai and Claude Desktop): Settings → Connectors → Add custom connector. Name it
Apify, paste the URL, then sign in to Apify. - Claude Code:
claude mcp add apify "https://mcp.apify.com/?tools=tidewell/doc-to-markdown" -t http - ChatGPT: add a custom MCP server named
Apifywith the URL above and OAuth. In the desktop app: Settings → MCP servers → Add server → Streamable HTTP. - Cursor, VS Code and other MCP clients:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=tidewell/doc-to-markdown"}}}. Clients without OAuth can add the headerAuthorization: Bearer <APIFY_TOKEN>.
Then ask in plain language, for example: "Use tidewell/doc-to-markdown to convert https://arxiv.org/pdf/1706.03762v7 to Markdown and summarise section 3." The actor appears as the tool tidewell--doc-to-markdown. Runs are billed to your Apify account at the prices below.
Full setup guide, examples and comparisons: plugin.benjaminbritton.co.uk/doc-to-markdown/
Pricing
Pay per event. You pay only for output.
| Event | Price | When |
|---|---|---|
| Document converted | $0.002 | Once per document that converts successfully |
| Page converted | $0.0003 | Per page that produced text (PDF page, slide, image or TIFF frame) |
| OCR page | +$0.0027 | Extra per page read with OCR, so an OCR page costs $0.003 in total |
DOCX, HTML, XLSX, EPUB and text files have no fixed pages, so they are billed at one page per 500 words (minimum 1).
| Example | Cost |
|---|---|
| 10-page digital PDF | $0.005 |
| 100-page digital PDF | $0.032 |
| 10-page scanned PDF (OCR) | $0.032 |
| 1,000 one-page Word memos | $2.30 |
| 1,000 photographed receipts (OCR) | $5.00 |
Failed downloads, corrupt, encrypted, unsupported and empty files cost nothing, and neither do pages without text. The Actor respects your maximum cost per run. It caps pages before converting, so it never does work it cannot bill.
FAQ
Is the text from digital PDFs OCR'd? No. Pages with a text layer are read directly, which is exact and about 10x faster. OCR runs only on pages without usable text, or on every page if you set forceOcr.
How good is table extraction? Tables with ruling lines (Word, Excel and most business PDFs) come out as Markdown tables. Tables drawn with whitespace alone, such as many academic papers, come out as aligned text lines. Word, PowerPoint, Excel and HTML tables are always converted.
Does it handle two-column PDFs? Yes. Reading order follows pdfminer's layout analysis, so columns are read one after the other instead of line by line across the page.
Which OCR languages are available? eng, deu, fra, spa, ita, por, nld, pol, ces, swe, dan, nor, fin, tur, rus, ukr, ell, ara, heb, hin, chi_sim, chi_tra, jpn, kor, vie. You can combine them, e.g. ["eng", "deu"].
What about password-protected PDFs? Set pdfPassword. Without it the document fails with encrypted_pdf and is not charged.
How big can files be? 100 MB by default, and up to 1,000 MB with maxFileSizeMb. Each document runs in its own process with a timeout, so one broken file cannot stop the run.
Are my documents stored? Only in your own Apify run storage, as the dataset and key-value store, under your retention settings. Nothing is sent to third-party AI services. Conversion runs entirely inside the Actor.
Is it a web crawler? No. It converts files you point it at. To crawl a site first, run a crawler and pass its dataset here with datasetId.
Which tokenizer is used for chunk sizes? cl100k_base (OpenAI), which gives a close estimate for most embedding models.
Keywords
pdf to markdown, docx to markdown, pptx to markdown, xlsx to markdown, html to markdown, document parser, document to markdown, pdf text extractor, pdf parser api, ocr, ocr api, scanned pdf to text, image to text, tesseract, rag, rag chunking, llm ready, llm document loader, langchain document loader, llamaindex, markitdown, unstructured alternative, docling alternative, chunk pdf for embeddings, pdf to json