Document to Markdown – DOCX, PPTX, XLSX
Pricing
from $2.70 / 1,000 converted documents
Document to Markdown – DOCX, PPTX, XLSX
Convert DOCX, PPTX, XLSX, XLS, ODS, CSV, EPUB, RTF, ODT, HTML, TXT and PDF files to clean Markdown with GFM tables, slides with speaker notes, sheets as tables or JSON rows. $3 per 1,000 documents (up to 20 pages or 10,000 rows each).
Pricing
from $2.70 / 1,000 converted documents
Rating
0.0
(0)
Developer
SFOLN LLC
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 hours ago
Last modified
Categories
Share
Document to Markdown converts office documents, spreadsheets, e-books and PDFs into clean Markdown for LLMs, RAG and search: Word headings, lists and tables become GFM, PowerPoint decks become one section per slide with speaker notes, Excel and OpenDocument sheets become Markdown tables (and, if you want, typed JSON rows), EPUBs become chapters. Give it document URLs, an uploaded file, data: URIs or Apify key-value store records; each document becomes one dataset record. The format is detected from the file itself, so wrong extensions do not matter.
- Formats: DOCX, PPTX, XLSX (and XLSB), XLS, ODS, CSV/TSV, EPUB, RTF, ODT, HTML, TXT (JSON and XML as code blocks) and PDF (text layer).
- What you get:
markdown,title,metadata(author, dates, language…),sections[]per page, slide, sheet or chapter,tables[]with optional JSONrows, word and page counts. - Price: $0.003 per document (
document, up to 20 pages or 10,000 rows), $0.0007 per further 20 pages or 10,000 rows (document-overage). No start fee; failed files are not charged. - Safe on untrusted files: zip bombs, XML entity tricks, macros and embedded objects are refused or ignored.
- Works everywhere: API, MCP for AI agents, Make, Zapier, n8n, schedules and webhooks.
Input
| Field | Type | Default | Description |
|---|---|---|---|
documentUrls | string[] | – | Documents, one per line: http(s) URLs, data: URIs (base64) or key-value store records (kvs://<storeId>/<key> or the record's API URL, read with the run's token). Max 25 MB per office file, 50 MB per PDF. |
documentFile | string | – | Upload a document from your computer (Apify Console) or give its URL; converted with the URLs above. |
includeSections | boolean | true | Return sections[]: one entry per PDF page, slide, sheet or chapter. |
includeRows | boolean | false | Spreadsheets: also return each sheet's rows as JSON objects keyed by the header row (tables[].rows). |
includePlainText | boolean | false | Also return text (no Markdown syntax). |
password | string (secret) | – | Password for encrypted PDFs (never written to the output). |
maxPages | integer | 1000 | Pages per document (PDF pages, slides, or 3,000-character pages of text), 1–5000. |
maxRows | integer | 100000 | Non-empty spreadsheet rows per document, all sheets together (1–200,000; give the run 2 GB of memory above about 100,000 rows). |
maxSheets | integer | 50 | Sheets per workbook (1–200). |
maxItems | integer | 1000 | Safety cap on documents per run; extra ones are ignored and counted as inputItemsDropped in OUTPUT. |
concurrency | integer | 2 | Documents converted in parallel (1–5). |
{"documentUrls": ["https://raw.githubusercontent.com/mwilliamson/mammoth.js/791a189230977143cde64dbf9f1d24dce96ac8a1/test/test-data/tables.docx","https://example.com/files/q1-sales.xlsx"],"includeRows": true}
Output
One dataset record per document:
{"input": "https://example.com/files/q1-sales.xlsx","status": "ok","charged": true,"chargedEvent": "document","source": "document-converter","fetchedAt": "2026-10-14T10:00:00.000Z","url": "https://example.com/files/q1-sales.xlsx","fileName": "q1-sales.xlsx","fileSizeBytes": 8317,"sha256": "8b1a9953c4611296a827abf8c47804d7e6c49c6b6b0f6d9a8f5f1f4e2d3c4b5a","format": "xlsx","title": "Sales Workbook","metadata": { "title": "Sales Workbook", "author": "Example Author", "subject": null, "description": null, "keywords": [], "language": null, "publisher": null, "createdAt": "2026-04-01T09:00:00.000Z", "modifiedAt": "2026-04-01T09:00:00.000Z" },"language": null,"pageCount": 0,"rowCount": 4,"wordCount": 29,"charCount": 140,"markdown": "## Sales\n\n| Region | Q1 | Q2 | Total | Updated |\n| --- | --- | --- | --- | --- |\n| North | 1240 | 1310 | 2550 | 2026-04-02 |\n| South | 980 | 1002 | 1982 | 2026-04-03 |\n| West | 1105 | 1190 | 2295 | 2026-04-04 |","markdownUrl": null,"sections": [{ "type": "sheet", "index": 1, "title": "Sales", "markdown": "## Sales\n\n| Region | Q1 | Q2 | Total | Updated |\n…", "hidden": false, "rowCount": 4, "columnCount": 5 }],"tables": [{ "sheet": "Sales", "index": 1, "rowCount": 4, "columnCount": 5, "columns": ["Region", "Q1", "Q2", "Total", "Updated"], "headerRow": true, "hidden": false, "rows": [{ "Region": "North", "Q1": 1240, "Q2": 1310, "Total": 2550, "Updated": "2026-04-02" }] }],"tablesUrl": null,"truncated": false,"truncation": [],"billedBlocks": 1,"overageCharged": 0}
| Field | Description |
|---|---|
format | Detected format: docx, pptx, xlsx, xls, ods, csv, epub, rtf, odt, html, txt or pdf. |
markdown | The whole document. Word: headings, bold/italic, lists, links, GFM tables (first row as header). Slides: ## Slide N: Title, bullets with their levels, tables, > **Notes:**. Sheets: ## Sheet name + one table (dates as ISO, numbers as displayed). EPUB: chapters in reading order. PDF: pages separated by <!-- page N -->. Images become [Image: alt text] when they have alt text. |
sections[] | One entry per PDF page, slide (notes, hidden), sheet (rowCount, columnCount, hidden) or chapter, each with its Markdown. Word, ODT, RTF, HTML and text files have none. |
tables[] | Spreadsheets: per sheet columns (the header row, or column letters when the first row is data; always unique: a blank header becomes its column letter, a repeated one gets a _2, _3, … suffix), counts and, with includeRows, rows as typed JSON objects. |
metadata | Title, author, subject, description, keywords, declared language, publisher (EPUB), creation and modification dates. |
pageCount, rowCount | What was billed: PDF pages, slides or 3,000-character text pages; non-empty spreadsheet rows (counted from the converted document, also when truncation lists content missing from the record). |
truncated, truncation | Something was left out by a limit (pages, rows, sheets, columns, free plan or budget) and what; on a storage_failed error, also the content the key-value store did not save (how much of it the record holds). |
scannedPages | PDFs: pages without a text layer (use PDF OCR to Markdown for those). |
markdownUrl, textUrl, tablesUrl | Only for very large documents (a record above ~6 MB): the full content is saved in the run's key-value store and the record keeps the beginning. A file the store does not save (after retries) keeps a null URL and makes the record a storage_failed error. |
billedBlocks, overageCharged | Blocks of 20 pages / 10,000 rows billed, and the document-overage events among them. |
status: ok – converted (charged). not_found – the URL returned 404/410 (not charged). error – download failed, or the file is unsupported (unsupported_document, e.g. a legacy .doc or an image), password-protected (encrypted_document, wrong_password), corrupt (corrupt_document) or a zip bomb (zip_bomb); not charged. error with storage_failed – converted, but the key-value store did not save the full content of a very large document (after retries): the record keeps the beginning and the URLs of the files that were saved, truncation lists what is missing, and the document event is not charged (document-overage events already charged before the save are shown in overageCharged). skipped_invalid – not a URL, data: URI or key-value record, or too large; not charged. skipped_budget / skipped_free_plan – not processed because of your cost cap or the free-plan limit. The key-value store record OUTPUT holds the run totals.
Pricing
Store discounts: the FREE and BRONZE tiers use the list prices below. The document event receives 5% off on SILVER and 10% off on GOLD, PLATINUM and DIAMOND. The document-overage event stays at $0.0007 on every tier, and the platform start fee is unchanged. Check the Actor's Store pricing table for the prices currently in effect.
| Event | Price | When charged |
|---|---|---|
document | $0.003 per document ($3 / 1,000) | once per document with status: "ok"; covers up to 20 pages or 10,000 rows |
document-overage | $0.0007 per block ($0.70 / 1,000) | per further block of 20 pages or 10,000 rows of a converted document |
A 10-page Word file or a 30-slide deck costs $0.003 / $0.0037; a 45,000-row workbook $0.0058. Before each document the Actor checks that your Maximum cost per run still covers the document event, and it converts only as many further blocks as the remaining budget pays for (the record is then marked truncated, with a warning). Accounts on the Apify free plan can process up to 10 documents per run, the first 20 pages or 10,000 rows of each; upgrade to any paid plan for complete documents and batches.
Use with AI agents (MCP)
Use this Actor from Claude, ChatGPT, Cursor, VS Code or any other MCP client through the Apify MCP server. Add this server URL to your client and sign in with your Apify account when asked:
https://mcp.apify.com?tools=sfoln/document-to-markdown
To run the MCP server locally instead, start npx -y @apify/actors-mcp-server --tools sfoln/document-to-markdown with your APIFY_TOKEN in the environment.
Example prompt:
Convert https://raw.githubusercontent.com/mwilliamson/mammoth.js/791a189230977143cde64dbf9f1d24dce96ac8a1/test/test-data/tables.docx to Markdown with document-to-markdown and show me the table it contains.
Runs started by an agent are billed like any other run (see Pricing). Agents can cap what a run may spend with maxTotalChargeUsd.
API usage
Run the Actor and get its dataset items in one call (the call waits up to 5 minutes for the run to finish):
curl -X POST "https://api.apify.com/v2/acts/sfoln~document-to-markdown/run-sync-get-dataset-items" \-H "Authorization: Bearer <APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"documentUrls": ["https://raw.githubusercontent.com/mwilliamson/mammoth.js/791a189230977143cde64dbf9f1d24dce96ac8a1/test/test-data/tables.docx"]}'
For large inputs, start the run asynchronously and download the dataset when it has finished:
# 1. Start a run: the response holds the run ID (data.id) and its dataset ID (data.defaultDatasetId)curl -X POST "https://api.apify.com/v2/acts/sfoln~document-to-markdown/runs" \-H "Authorization: Bearer <APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"documentUrls": ["https://raw.githubusercontent.com/mwilliamson/mammoth.js/791a189230977143cde64dbf9f1d24dce96ac8a1/test/test-data/tables.docx"]}'# 2. Repeat until data.status is SUCCEEDED (each call waits up to 60 s)curl "https://api.apify.com/v2/actor-runs/<runId>?waitForFinish=60" -H "Authorization: Bearer <APIFY_TOKEN>"# 3. Download the results (format=json, csv or xlsx)curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json" -H "Authorization: Bearer <APIFY_TOKEN>"
Python (apify-client):
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("sfoln/document-to-markdown").call(run_input={"documentUrls": ["https://raw.githubusercontent.com/mwilliamson/mammoth.js/791a189230977143cde64dbf9f1d24dce96ac8a1/test/test-data/tables.docx"]})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item)
Instead of polling, you can attach a webhook to the run (see Integrations). The API tab of this page shows the same calls for JavaScript and the Apify CLI.
Integrations
- Make, Zapier and n8n: run
sfoln/document-to-markdownwith the Apify app (in n8n, the Apify node) and pass the dataset items to the next step of your workflow. - Google Sheets: add the items to a sheet with the Google Sheets step of Make, Zapier or n8n, or download them as CSV or Excel from the run's Storage tab or the API (
format=csvorformat=xlsx). - Webhooks: get a POST request when a run succeeds or fails: add a webhook in the Actor's Integrations tab, or pass
webhookswhen you start a run through the API. - Schedules: run it hourly, daily or weekly with the same input from Apify Schedules; every run keeps its results in its own dataset.
- More: Slack, Google Drive, GitHub, LangChain, LlamaIndex and other Apify integrations.
Related Actors
- PDF Text Extractor – PDF to Text & Markdown
- PDF OCR to Markdown Converter
- Document Parser – AI PDF & Image to JSON
- URL to Markdown & Article Extractor
See all our Actors on Apify Store.
FAQ
How much does it cost, and am I charged for failed items?
You pay per event at the prices under Pricing above, plus a run start fee of $0.00005 per GB of memory, with no subscription; platform usage is included. Errors and skipped items (skipped_… statuses) are never charged; a lookup that finds nothing is charged only where Pricing says so, and every record's charged field shows whether it was billed. Set Maximum cost per run to cap what a single run can spend.
How fresh is the data?
Each run queries its source while it runs, so you get what the source returns at that moment. Source-specific details, such as how often a provider refreshes its own database, are under Limits & notes.
What are the limits?
Per-run caps (for example the maximum number of items) are listed under Input, source limits under Limits & notes, and the free-plan limit under Pricing. For big batches, start the run asynchronously (see API usage) and read the dataset when it has finished.
Can I schedule runs or get results by webhook?
Yes. Create a schedule in Apify Console to run it hourly, daily or weekly with the same input, and add a webhook to get a POST request when a run finishes (see Integrations).
What does this Actor record about my runs?
This Actor records operational metrics about each run (item counts, duration, charges, error codes and a pseudonymous hash of your Apify user ID) to monitor reliability and costs. It never stores your input or results.
Which formats are supported?
DOCX, PPTX, XLSX, XLSB, XLS, ODS, CSV/TSV, EPUB, RTF, ODT, HTML, plain text (JSON and XML are returned as code blocks) and PDF (its text layer). The format is detected from the file's bytes, so a wrong extension does not matter. Legacy .doc and .ppt files are not supported: save them as DOCX or PPTX.
How are pages counted for pricing?
PDF pages and slides count as pages; for text documents a page is 3,000 characters of output (about 500 words); spreadsheets count non-empty rows. One document event covers 20 pages or 10,000 rows, each further block of 20 pages or 10,000 rows is one document-overage event ($0.0007).
Can I get spreadsheet data as JSON?
Yes: set includeRows and every sheet's rows come back in tables[].rows as JSON objects keyed by the header row, with numbers, booleans and ISO dates typed.
Is it safe to convert untrusted files?
Archives are checked before they are unpacked (entry count and uncompressed size, so zip bombs are refused), XML entity declarations are refused and nothing in a document is executed. Images, macros and embedded objects are ignored.
How do I get help?
Open an issue in the Actor's Issues tab and include the link to the run, so we can reproduce the problem. Feature requests are welcome there too.
Limits & notes
- Text is converted, not rendered: images, charts, SmartArt, macros and embedded objects are left out (images keep their alt text). Formulas return their last computed values. Merged cells keep their value in the first cell.
- PDF pages are read from the text layer; scanned pages have none (see
scannedPages). A damaged page that cannot be decoded is left empty with a warning and is not counted inpageCountor billed. - Limits per document: 25 MB office files, 50 MB PDFs, 10,000 archive entries, 200 columns and 1,000 characters per spreadsheet cell,
maxPages,maxRowsandmaxSheets. Uncompressed content: 64 MB for Word, PowerPoint, OpenDocument text and EPUB files (32 MB per part), 150 MB for spreadsheets (100 MB per part). Archives over these caps, and parts that unpack past their declared size, are refused aszip_bomb. - Very large inputs are cut, not refused (the record is then
truncated, with the reason intruncation): HTML files and EPUB chapters after 3,000,000 characters of HTML; Word files after the document XML the run's memory can convert (about 2.5 MB of it on 1 GB of memory, a few hundred pages of text; give the run more memory for longer documents), Word footnotes, endnotes and comments above 2 MB of XML are left out; spreadsheet repeats of OpenDocument files after 1,000,000 repeated cells; spreadsheet sheets after their firstmaxRowsrows and a tenth more (at least 1,000 more): blank rows are skipped and not counted inmaxRows, but only that many of them are read. - Legacy binary Word (.doc) and PowerPoint (.ppt) files and password-protected Office files are not supported.
- Downloads follow at most 5 redirects and only reach public hosts (no private or local addresses).