Office Documents to Markdown (DOCX, PPTX, XLSX)
Pricing
$1.50 / 1,000 document converteds
Office Documents to Markdown (DOCX, PPTX, XLSX)
Give it DOCX, PPTX, XLSX (and ODT, ODP, ODS) URLs, get clean Markdown, plain text, tables as JSON and metadata for each one, ready for LLM and RAG pipelines. Slides with speaker notes, one table per sheet. No OCR. You pay only for documents that were converted.
Pricing
$1.50 / 1,000 document converteds
Rating
0.0
(0)
Developer
Francesco Antonio Russo
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Office Documents to Markdown – DOCX, PPTX, XLSX (and ODT, ODP, ODS) for LLMs and RAG
Give it links to Word, PowerPoint and Excel files and get clean Markdown, plain text, every table as JSON, speaker notes and metadata back, one record per file. No browser, no OCR, no API key, no LLM: the files are read directly, so results are fast and the same file always gives the same answer.
Price: $0.0015 per converted document (that is $1.50 per 1,000 documents), one flat price per file whether it has 2 pages or 200 slides. Files that cannot be converted are free.
Who it is for
- RAG and AI-agent builders who need Office files as text a model can read, with headings, lists and tables still in place and metadata to cite.
- Teams that index documents: intranets, knowledge bases, search, compliance archives, public-sector data portals.
- Data people who want the tables of Word files and the sheets of workbooks as JSON, not as a screenshot.
- Scheduled jobs that watch a list of public documents (the
sha256of each file tells you when one changed).
For PDFs use the sister Actor PDF to Text & Markdown Extractor (far003/pdf-to-text-markdown-extractor): same record style, same price idea. If you send a PDF here you get a free record that says so.
What it reads
| File | What you get |
|---|---|
Word (.docx, .docm, .dotx) | Headings (from styles and outline levels), paragraphs, bold / italic / strike / super- and subscript, links, bullet and numbered lists (nested, with Word's own numbering, "2.1" legal numbers kept), tables with merged cells, footnotes and endnotes, text boxes, code in monospace fonts, quotes. Tracked changes are read as accepted (deletions out). |
PowerPoint (.pptx, .ppsx, .pptm) | One section per slide, in presentation order: the title, the text in reading order (top to bottom, then left to right), bullets with their levels, tables, charts as a table of their values, SmartArt text, alt text of pictures, and the speaker notes. Hidden slides are kept and marked. |
Excel (.xlsx, .xlsm, .xltx) | One table per sheet. Cells show what Excel shows: dates as dates, 12.5% as 12.5%, 1234.5 as 1,234.50 where the cell format says so. Formulas use the value Excel saved. Empty rows and empty leading columns are dropped. The row limit is yours to set and the record says when it cut. |
OpenDocument (.odt, .odp, .ods) | The same output as the three above. Spreadsheet cells carry their displayed text. |
The type is recognised from the content of the file (the ZIP package and its content types), never from the extension or the Content-Type the server sends: a DOCX served as report.pdf is still read as a DOCX.
Quick start
- Paste the links into Document URLs.
- Click Start.
- Open the Overview tab for the list, Markdown and text for the content, Tables (JSON), Slides and notes, or download the dataset as JSON, CSV or Excel.
Example input
This is the real example prefilled in the input form:
{"urls": ["https://www.epa.gov/system/files/documents/2025-05/drinking-water-erp-template.docx","https://raw.githubusercontent.com/apache/poi/trunk/test-data/slideshow/SampleShow.pptx","https://www.irs.gov/pub/irs-soi/23dbalabama.xlsx"]}
Run result: 3 records (3 ok), 3 charged = 3 × $0.0015 = $0.0045.
Example output (a real run, shortened)
A Word file (a US EPA template, 29 pages, 38 tables). Every field is always present; fields that do not apply are null:
{"url": "https://www.epa.gov/system/files/documents/2025-05/drinking-water-erp-template.docx","status": "ok","error": null,"charged": true,"httpStatus": 200,"fileName": "drinking-water-erp-template.docx","sizeBytes": 217904,"sha256": "7f5e54a224a47ffa81cf1269996d7847c531cf1051d5fc1dc9510d12b95f7ea3","format": "docx","title": "Emergency Response Plan Template","titleSource": "metadata","language": "en-US","createdAt": "2025-05-19T13:39:00.000Z","modifiedAt": "2025-05-19T17:27:00.000Z","application": "Microsoft Office Word","pageCount": 29,"tableCount": 38,"imageCount": 0,"wordCount": 5407,"hasMacros": false,"truncated": false,"warnings": []}
Its markdown field (an excerpt of the real output):
### Utility Information| **PWSID** | *123456* || --- | --- || **Owner** | *Town of Anytown* || **Total population served and total service connections** | *7,500 served - 2,500 connections* |☐ Site plans and “as built” drawings for the following components of your system (as applicable):- Pumping and storage facilities- Reservoir facilities- Water treatment facilities
A PowerPoint file (Apache POI sample deck): markdown has a section per slide, notes included, and slides[1] is
{"number": 2,"title": "This is the second slide","hidden": false,"text": "- It has bullet points on it\n- They’re fun, aren’t they?\n- Especially in a different font like Arial Black at 16 point!","notes": "These are the notes of the 2nd slide\nTHIS LINE IS BOLD"}
An Excel file (IRS statistics): markdown is a table, tables[0].rows the same cells as arrays, sheets the facts about the sheet:
{ "name": "Sheet1", "state": "visible", "rowCount": 17, "columnCount": 6, "rowsReturned": 17, "truncated": false }
## Sheet: Sheet1| Alabama, Fiscal Year 2023 | | | | | || --- | --- | --- | --- | --- | --- || | Number of returns filed | Number of returns filed electronically | Internal Revenue gross collections (thousands of dollars) | ... || Total [1] | 3,471,529 | 2,690,994 | 35,829,649 | 1,706,757 | 6,809,200 || Estate tax | 391 | n/a | 309,381 | 27 | 12,315 |
Input
| Field | Default | What it does |
|---|---|---|
urls | – | Addresses of the files. Bad entries get a free invalid record, they never stop the run. |
outputFormats | all four | markdown, text, tables (every table as JSON), slides (one object per slide). Choose fewer for lighter records. |
maxRowsPerSheet | 1000 | Spreadsheets: non-empty rows converted per sheet (up to 100,000). The record tells you the total and that it was cut. The memory of the run can cut earlier on a very big sheet (see the FAQ). |
includeHiddenSheets | off | Hidden sheets are skipped (and the warnings say so) unless you turn this on. |
includeSpeakerNotes | on | Notes after each slide. |
maxFileMegabytes | 50 | Bigger files are refused (free, too_large), the download is stopped as soon as the limit is passed. |
respectRobotsTxt | on | The robots.txt of the site is read before the first request and followed for every redirect. |
requestsPerSecond | 2 | Per host. A Crawl-delay in robots.txt can only slow it down. |
maxConcurrency | 3 | Documents converted at the same time. |
timeoutSecs | 60 | Download plus conversion, per document. |
Output fields
One record per input address, in the Overview view; the same record has everything.
| Field | Meaning |
|---|---|
url, finalUrl | The address as given and after redirects (credentials removed). |
status | See the table below. |
error | Why there is no (or only a partial) result, in one sentence; null when all went well. |
charged | true only for ok and partial. |
httpStatus, fileName, sizeBytes, sha256 | About the download. sha256 is the hash of the file: use it to detect changes. |
format | docx, pptx, xlsx, odt, odp or ods, as found in the content. |
title, titleSource | The title of the file properties; if there is none, or it is a program default ("Document1", "Microsoft Word - …"), the first heading (first-heading) or first slide title (first-slide-title). |
subject, keywords, language | From the file properties; language also from the language of the text when the file does not say (BCP 47, e.g. en-US). |
createdAt, modifiedAt, application | Dates (ISO 8601, UTC) and the program that made the file. |
pageCount | Word and Writer only, as declared by the file (Word does not store the layout's pages, so the number may be stale); null otherwise. |
slideCount, sheetCount | Slides; sheets (hidden ones included). |
tableCount, imageCount | Tables converted (one per worksheet); pictures found (not converted). |
wordCount, charCount | Of the converted text. |
hasMacros | The file holds macros. They are never read or run. |
truncated, fullTextKey | true when rows were cut or the text was too long for a record; then the whole Markdown is in the key-value store under fullTextKey. |
markdown, text | The document as Markdown and as plain text. |
tables | [{ index, name, location, rowCount, columnCount, rows }], rows being arrays of cell texts. |
slides | PPTX/ODP: [{ number, title, hidden, text, markdown, notes }]. |
sheets | XLSX/ODS: [{ name, state, rowCount, columnCount, rowsReturned, truncated }]. |
warnings | What the conversion could not carry over (see below). |
extractedAt | When the record was made. |
Statuses
status | Meaning | Charged |
|---|---|---|
ok | Converted. | yes |
partial | Converted, but a part could not be read (a damaged slide or sheet; the warnings name it). | yes |
empty | A valid file with no text (only pictures, or blank). | no |
password_protected | The file is encrypted. It cannot be read. | no |
unsupported_format | Legacy .doc / .xls / .ppt, RTF, EPUB, XLSB, OpenDocument drawings… The message says what it is and to save it as DOCX / PPTX / XLSX. | no |
not_office | The URL is not an Office document: an HTML page, a PDF, a ZIP, an empty answer. | no |
corrupt | A damaged or truncated file. | no |
unsafe_archive | Refused for safety: a zip bomb (absurd compression ratio, or parts larger than they declare). | no |
too_large | Over maxFileMegabytes, or a document whose XML is too big to be read safely. | no |
blocked | robots.txt forbids it, the site answered 401/403/429 or a bot-challenge page, or the address leads to a private network. Nothing is done to get around it. | no |
unreachable | The download failed (DNS, timeout, 404, 5xx after one retry). | no |
invalid | Not a usable web address. | no |
Warnings: what is not carried over
The conversion tells you, record by record, what it left out, so you do not have to guess:
- Pictures (counted in
imageCount; the author's alt text is kept when it says something) – there is no OCR and no image description. - Charts in Word and Excel (in PowerPoint a chart becomes a table of its values), equations (read as plain text), SmartArt (its text only), comments, headers and footers, the table of contents (the headings are there).
- Formulas are not calculated: the value Excel saved is shown; a formula without a saved value is an empty cell, and the warning counts them.
- Macros are flagged (
hasMacros) and never read. - Hidden sheets, rows cut at
maxRowsPerSheet(or by the memory of the run on a very big sheet), columns beyond 500.
Pricing
One event: document – $0.0015 for each file converted (ok or partial). Nothing else is charged: no start fee, no per-page, per-slide or per-row price.
| You convert | Cost |
|---|---|
| 1 document | $0.0015 |
| 100 documents | $0.15 |
| 1,000 documents | $1.50 |
| 1,000 addresses where 150 are broken links or protected files | $1.275 (850 × $0.0015) |
Platform usage (compute) is included in that price: you pay nothing else.
Spending limit: if you set a maximum cost per run, the Actor stops cleanly when it is reached ("spending limit reached" in the run summary) and does not download more files.
Safe and polite by design
- Privacy: the author and last editor fields of the file properties are read by nobody: they are not in the output (personal data).
- robots.txt is respected (checked for every site and every redirect), with an honest
User-Agentthat names the bot. No proxies, no disguise, no CAPTCHA tricks: a block page is reported asblocked. - Private networks are never reached, not even through a redirect or a name that resolves to a private address.
- Zip bombs and absurd files are refused: the size is checked while the file is unpacked, not just as the header declares it.
- Nothing is executed. Macros, scripts, links and embedded objects are never run or followed.
- A restarted run does not deliver or charge the same document twice.
FAQ
Does it do OCR? No. A scanned image inside a Word file stays an image (counted, with its alt text if any). For text in images use an OCR Actor.
Can it read an old .doc, .xls or .ppt? No, those are a different (binary) format. You get a free record saying so; save the file as DOCX / XLSX / PPTX once and run again.
What about password-protected files? They cannot be read; the record is password_protected and free.
Why is pageCount null for my Word file? Word stores the page count only when it saved the file with it. We report what the file declares and never invent a number. Slides and sheets are always counted.
Are the numbers in my spreadsheet what Excel shows? Dates, percentages, thousands separators, decimals and currency follow the cell format. Formulas are not recalculated. If a number looks different from what you see in Excel, tell us the format code.
Is there a memory limit? Yes, and a big file never crashes the run. Spreadsheets (XLSX, ODS) are read as a stream, so the size of the file is not the limit: the rows you get back are, because they are kept in memory until the record is written. With the default 1 GB that is about 40,000 rows of 10 short columns (numbers, dates) or about 18,000 rows of 17 text columns (a long public register is that kind of file). Past that the sheet is cut: the record says truncated: true, sheets[].rowCount has the real total, and a warning names the sheet ("Rows cut to stay within the memory of the run: give the run more memory or lower maxRowsPerSheet"). A cut file is still converted and charged like any other; the other files of the run are not affected. More memory gives proportionally more rows (4 GB about four times as many). Files refused for safety are free (too_large) with the reason in error: an XLSX or ODS sheet over 256 MB unpacked, an XLSX whose table of shared strings does not fit in a quarter of the run's heap (about 90 million characters of text on 1 GB), a Word, PowerPoint, Writer or Impress file whose XML is over 30 MB unpacked, or a package over 400 MB unpacked.
My workbook is huge. The first maxRowsPerSheet non-empty rows of each sheet are converted, and sheets[] has the real row counts. Raise the limit (up to 100,000) if you need more, and give the run more memory if the record says the rows were cut by memory; very long outputs are saved in the key-value store.
Does it follow links inside the documents? No. Links are kept as Markdown links; only the addresses you give are downloaded.
Is a file I put on a private server readable? Only public http(s) addresses. Private and local addresses are refused by design, and credentials in a URL are removed and not used.
How does it compare with Microsoft's MarkItDown? MarkItDown is an excellent open-source library; this Actor is the hosted version for batches: URLs in, records out, with the safety limits, the per-file status, the tables as JSON, the notes per slide and the "what was lost" warnings built in.
Integration
Run it from the Apify API, the CLI, or your own code.
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('far003/office-docs-markdown').call({urls: ['https://example.org/handbook.docx', 'https://example.org/q3-results.pptx'],outputFormats: ['markdown', 'tables'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const doc of items) {if (doc.status === 'ok') console.log(doc.title, doc.wordCount, doc.markdown.slice(0, 200));else console.warn(doc.url, doc.status, doc.error);}
Python
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("far003/office-docs-markdown").call(run_input={"urls": ["https://example.org/handbook.docx"],"maxRowsPerSheet": 5000,})for doc in client.dataset(run["defaultDatasetId"]).iterate_items():print(doc["status"], doc["format"], doc["title"], doc["wordCount"])
Chunking for RAG: split markdown on \n## for slides and sheets, on headings for Word files; slides[].markdown and tables[] are already cut at natural boundaries.
Run summary (totals per status, charged, skipped) is stored under the key SUMMARY of the default key-value store.
Related
- PDF to Text & Markdown Extractor – the same job for PDFs: reading order, columns, headings, tables, bookmarks.