Document to Markdown – DOCX, PPTX, XLSX avatar

Document to Markdown – DOCX, PPTX, XLSX

Pricing

from $2.70 / 1,000 converted documents

Go to Apify Store
Document to Markdown – DOCX, PPTX, XLSX

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

SFOLN LLC

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 hours ago

Last modified

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 JSON rows, 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

FieldTypeDefaultDescription
documentUrlsstring[]–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.
documentFilestring–Upload a document from your computer (Apify Console) or give its URL; converted with the URLs above.
includeSectionsbooleantrueReturn sections[]: one entry per PDF page, slide, sheet or chapter.
includeRowsbooleanfalseSpreadsheets: also return each sheet's rows as JSON objects keyed by the header row (tables[].rows).
includePlainTextbooleanfalseAlso return text (no Markdown syntax).
passwordstring (secret)–Password for encrypted PDFs (never written to the output).
maxPagesinteger1000Pages per document (PDF pages, slides, or 3,000-character pages of text), 1–5000.
maxRowsinteger100000Non-empty spreadsheet rows per document, all sheets together (1–200,000; give the run 2 GB of memory above about 100,000 rows).
maxSheetsinteger50Sheets per workbook (1–200).
maxItemsinteger1000Safety cap on documents per run; extra ones are ignored and counted as inputItemsDropped in OUTPUT.
concurrencyinteger2Documents 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
}
FieldDescription
formatDetected format: docx, pptx, xlsx, xls, ods, csv, epub, rtf, odt, html, txt or pdf.
markdownThe 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.
metadataTitle, author, subject, description, keywords, declared language, publisher (EPUB), creation and modification dates.
pageCount, rowCountWhat 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, truncationSomething 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).
scannedPagesPDFs: pages without a text layer (use PDF OCR to Markdown for those).
markdownUrl, textUrl, tablesUrlOnly 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, overageChargedBlocks 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.

EventPriceWhen 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 ApifyClient
client = 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-markdown with 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=csv or format=xlsx).
  • Webhooks: get a POST request when a run succeeds or fails: add a webhook in the Actor's Integrations tab, or pass webhooks when 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.

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 in pageCount or 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, maxRows and maxSheets. 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 as zip_bomb.
  • Very large inputs are cut, not refused (the record is then truncated, with the reason in truncation): 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 first maxRows rows and a tenth more (at least 1,000 more): blank rows are skipped and not counted in maxRows, 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).