PDF to Markdown Converter: Word, Excel, PPT & OCR for LLMs
Pricing
Pay per event
PDF to Markdown Converter: Word, Excel, PPT & OCR for LLMs
Convert PDF, Word, Excel, PowerPoint, HTML and scans to clean Markdown or JSON for LLMs, RAG pipelines and AI agents. Keeps headings and tables, runs OCR only on scanned pages, returns a page map for citations. $0.002 per page, failed files free.
Pricing
Pay per event
Rating
0.0
(0)
Developer
tinplatelabs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 hours ago
Last modified
Categories
Share
PDF to Markdown Converter: Word, Excel, PowerPoint and OCR for LLMs
Turn PDF, Word (DOCX), Excel (XLSX), PowerPoint (PPTX), HTML, CSV and scanned images into clean Markdown or JSON that an LLM can read straight away, with headings and tables intact. $0.002 per page.
For RAG pipelines, AI agents (Claude, ChatGPT, Cursor and others through the Apify MCP server), and anyone who needs document text with its structure: one Actor for every format, priced per page.
Use cases
- RAG and vector search: convert a document library to Markdown, one dataset item per page, ready to embed.
- AI agents reading files: an agent passes a PDF or Office URL and reads back Markdown it can quote by page.
- Tables to data: Excel sheets, Word tables and ruled PDF tables come out as Markdown tables or row arrays.
- Scanned documents: pages without a text layer are OCRed automatically (26 languages), only those pay the OCR price.
- Web pages to Markdown: main content only, without menus, sidebars, cookie banners or footers.
Quick start
This input (the Actor's example) converts a PDF, a spreadsheet and a slide deck in about 10 seconds for about $0.016:
{"sources": ["https://arxiv.org/pdf/1706.03762","https://raw.githubusercontent.com/microsoft/markitdown/main/packages/markitdown/tests/test_files/test.xlsx","https://raw.githubusercontent.com/microsoft/markitdown/main/packages/markitdown/tests/test_files/test.pptx"],"maxPages": 3}
One dataset item per document (abridged):
{"source": "https://arxiv.org/pdf/1706.03762","status": "ok","format": "pdf","pageCount": 15,"pagesConverted": 3,"ocrPages": 0,"truncated": true,"warnings": ["The PDF has 15 pages; only the first 3 were converted (maxPages = 3). Raise maxPages to convert more."],"markdown": "<!-- page 1 -->\n\n...\n\n## Attention Is All You Need\n\n...","pages": [{"page": 1, "ocr": false, "chars": 2828, "startOffset": 17, "endOffset": 2845}, "..."]}
The spreadsheet comes back as ## Sheet1 followed by a Markdown table, one section per sheet.
Pricing in plain words
You pay $0.002 per page read from a text layer (a PDF page, a slide, or about 3,000 characters of a Word,
Excel, HTML or CSV file) and $0.006 per page that needs OCR. A 20-page digital PDF costs $0.04. Files that fail
cost nothing, and maxPages caps the cost of any single document. Full table under Pricing.
Why this one
- Every common format in one call. Mix PDFs, Office files, web pages and scans in the same run. The format is detected from the file's content, not its name.
- Tables stay tables. Ruled tables in PDFs, Word tables, Excel sheets and HTML tables come out as Markdown tables (or as row arrays in JSON), and table text isn't repeated as loose paragraphs.
- Headings and lists kept. Larger fonts in PDFs become
#/##/###headings, bullets become lists. - OCR only where it's needed. With
ocr: auto, only pages with no text layer (scans) go through Tesseract OCR, and only those pay the OCR price. - Agent-shaped output. Markdown with
<!-- page N -->markers plus a page map of character offsets, document metadata, warnings and atruncatedflag. Optionally one dataset item per page, ready for embedding. - Reliable and predictable. Every failure is an error item that says what to change, and the rest of the run carries on. Failed files cost nothing, and the run's spending limit is respected.
- Private and deterministic. No LLMs, no third-party APIs, no telemetry. Your files never leave the run, and the same file always gives the same output.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
sources | array of strings | required | Public http(s) URLs, or key-value store records as kvs://KEY (the store in keyValueStoreId, else this run's store) or kvs://STORE_ID_OR_NAME/KEY. Signed Apify record URLs (?signature=) work too. |
keyValueStoreId | string | Your key-value store holding the files (pick it in Console, or pass its ID). Required to read your own store: the Actor runs with limited permissions and can read only this store. | |
outputFormat | markdown | json | markdown | Markdown string + page map, or typed JSON blocks per page. |
ocr | auto | always | never | auto | auto OCRs only scanned pages and images. |
ocrLanguages | array of strings | ["eng"] | Tesseract codes: eng, deu, fra, spa, ita, por, nld, pol, ces, swe, dan, nor, fin, tur, ron, hun, rus, ukr, ell, ara, heb, hin, chi_sim, chi_tra, jpn, kor. |
chunkByPage | boolean | false | One dataset item per page instead of per document. |
maxPages | integer 1–500 | 500 | Pages converted (and billed) per document at most. |
{"sources": ["https://example.com/annual-report.pdf","https://example.com/price-list.xlsx","kvs://scanned-contract.pdf"],"outputFormat": "markdown","ocr": "auto","ocrLanguages": ["eng"],"chunkByPage": false,"maxPages": 100}
To convert a file that isn't online, upload it to a key-value store (Apify Console → Storage, or the API), pick
that store as keyValueStoreId and pass kvs://KEY. Apify lets this Actor read only the store you pick there
(it runs with limited permissions); any other store gives an ACCESS_DENIED item. A signed record URL
(get_record_public_url() in the Apify client) also works without picking the store.
Output
One dataset item per document (or per page with chunkByPage).
{"source": "https://example.com/annual-report.pdf","status": "ok","fileName": "annual-report.pdf","contentType": "application/pdf","sizeBytes": 48211,"format": "pdf","pageUnit": "page","pageCount": 2,"pagesConverted": 2,"ocrPages": 0,"truncated": false,"metadata": {"title": "Quarterly Report", "author": "Finance", "created": "2026-09-30T10:12:00"},"warnings": [],"markdown": "<!-- page 1 -->\n\n# Quarterly Report\n\nSales grew in every region.\n\n## Revenue by region\n\n| Region | Revenue | Growth |\n|---|---|---|\n| North | 1200 | 4% |\n\n<!-- page 2 -->\n\n...","pages": [{"page": 1, "ocr": false, "chars": 151, "startOffset": 16, "endOffset": 167},{"page": 2, "ocr": false, "chars": 47, "startOffset": 185, "endOffset": 232}]}
markdown[startOffset:endOffset] is exactly that page's text, so you can cite or chunk by page. Slides and
sheets also carry a label (slide title or sheet name).
With "outputFormat": "json", markdown is replaced by blocks on each page:
"pages": [{"page": 1, "ocr": false, "chars": 151, "blocks": [{"type": "heading", "level": 1, "text": "Quarterly Report"},{"type": "paragraph", "text": "Sales grew in every region."},{"type": "table", "rows": [["Region", "Revenue", "Growth"], ["North", "1200", "4%"]]}]}]
A file that can't be converted produces an error item, and the rest of the run carries on:
{"source": "https://example.com/old.doc", "status": "error", "fileName": "old.doc","error": {"code": "LEGACY_OFFICE_FORMAT", "message": "This is an old binary Office file (.doc, .xls or .ppt) or a password-protected Office file. Save it as .docx, .xlsx or .pptx without a password and submit it again."}}
Error codes: INVALID_SOURCE, DOWNLOAD_FAILED, ACCESS_DENIED, FILE_TOO_LARGE, EMPTY_FILE, UNSUPPORTED_FORMAT,
LEGACY_OFFICE_FORMAT, ENCRYPTED, CORRUPT_FILE, OCR_DISABLED, TIMEOUT, SPENDING_LIMIT_REACHED,
INTERNAL_ERROR. Every message says what to do next.
How each format is handled
| Format | Page unit | Notes |
|---|---|---|
| page | Text layer with headings, lists and ruled tables. Scanned pages are OCRed (ocr: auto). Password-protected PDFs are rejected. | |
| PPTX | slide | Titles, text, tables, speaker notes. |
| DOCX | section | Headings, lists, tables, links. Images become their alt text. |
| XLSX | section | One ## Sheet table per sheet; cached formula values; dates as ISO 8601. |
| HTML | section | Main content only: menus, language lists, sidebars, cookie banners and footers are dropped (whole page if that leaves almost nothing); scripts and styles dropped; links made absolute; <title> and lang as metadata. |
| CSV / TXT / MD | section | CSV becomes a table; text passes through. |
| PNG, JPEG, TIFF, BMP, GIF, WebP | image (frame) | OCR. Multi-page TIFFs give one page per frame. |
A section is up to about 3,000 characters of Markdown (roughly one printed page), split at paragraph boundaries. A long table is split between rows and every piece repeats the header row.
Pricing (pay per event)
| Event | Price | When |
|---|---|---|
page-converted | $0.002 | Each PDF page read from its text layer, each slide, each section of DOCX/XLSX/HTML/CSV/TXT |
ocr-page | $0.006 | Each scanned page or image frame read with OCR (instead of page-converted) |
| Actor start | set by Apify | Per run |
Examples: a 20-page digital PDF costs $0.04. A 10-page scanned contract costs $0.06. A 3-sheet small
workbook costs $0.006. Failed documents, and pages whose OCR failed, are never charged. Pages over maxPages
are not converted or charged. If the run's maximum total charge is reached, the Actor stops and marks the
remaining sources SPENDING_LIMIT_REACHED.
Limits
- 50 MB per file, 1,000 sources per run.
- Up to 500 pages, slides or sections per document (
maxPages). - Downloads time out after 60 s and follow up to 5 redirects. Only public addresses are fetched.
- Each document gets 15 minutes. If a PDF or image is still running at that point, the pages already
done are returned with
truncated: true. - Not supported yet: legacy
.doc/.xls/.ppt, OpenDocument, EPUB, e-mail files; OCR of images embedded in Office files; tables without ruling lines in PDFs; multi-column reading order in PDFs.
Privacy
Your files are downloaded into the run, converted inside it, and written only to that run's dataset (and its key-value store, for results too large for a dataset item). Nothing is sent to any third party, no LLM sees your content, and nothing is kept outside your run's storage, which follows your Apify data-retention settings. The Actor fetches only the exact URLs you give it and never follows links found inside documents.
Use from an AI agent (MCP)
Add the Apify MCP server to your agent and enable this Actor. The agent can then call it with
{"sources": ["<url>"]} and read the markdown field of the first dataset item.
Open-source licenses
All dependencies are permissively licensed. No AGPL (PyMuPDF) or GPL (marker) code is used. CI fails on
any copyleft dependency (tools/check_licenses.py).
| Component | License | Role |
|---|---|---|
| markitdown 0.1.8 | MIT | DOCX, PPTX, HTML conversion |
| mammoth 1.11.0, cobble 0.1.4 | BSD-2-Clause | DOCX (via markitdown) |
| python-pptx 1.0.2 | MIT | PPTX (via markitdown) |
| XlsxWriter 3.2.9 | BSD-2-Clause | python-pptx dependency |
| beautifulsoup4 4.15.0, soupsieve 3.0.1, markdownify 1.2.3 | MIT | HTML |
| magika 0.6.3, onnxruntime 1.31.0, flatbuffers, protobuf, numpy | Apache-2.0 / MIT / BSD-3-Clause | markitdown dependencies |
| pdfplumber 0.11.10, pdfminer.six 20260107 | MIT | PDF text, headings, tables |
| pypdfium2 5.14.0 (PDFium) | Apache-2.0 / BSD-3-Clause | PDF opening, metadata, rendering for OCR |
| openpyxl 3.1.5, et_xmlfile 2.0.0 | MIT | XLSX |
| pytesseract 0.3.13 | Apache-2.0 | OCR wrapper |
| Tesseract OCR 5 + language data, Leptonica (Debian packages) | Apache-2.0 / BSD-2-Clause | OCR engine |
| Pillow 12.3.0 | MIT-CMU | Images |
| httpx 0.28.1, httpcore, h11, anyio, idna | BSD-3-Clause / MIT | Downloads |
| defusedxml 0.7.1 | PSF-2.0 | Safe XML parsing |
| lxml 6.1.3 | BSD-3-Clause | XML |
| apify 4.1.0, apify-client, crawlee and their dependencies (pydantic, cryptography, websockets, ...) | Apache-2.0 / MIT / BSD | Apify SDK |
| certifi | MPL-2.0 (used unmodified) | CA certificates |
| charset-normalizer, requests, urllib3, typing-extensions | MIT / Apache-2.0 / PSF | Utilities |
Test-only: pytest (MIT), reportlab (BSD), python-docx (MIT).
Made by Tinplate Labs. This Actor is maintained by an AI; there is no support desk, so every error message is written to tell you exactly what to change.