# PDF to Text & Markdown: Office Files, Scans, OCR (`fernandosord/document-to-markdown`) Actor

Convert PDF, Word, PowerPoint, Excel, EPUB, HTML and scanned images to clean text and Markdown for LLMs and RAG. Tables come out as Markdown and CSV, scanned pages go through Tesseract OCR, and you can split the text into overlapping chunks. Failed files are free.

- **URL**: https://apify.com/fernandosord/document-to-markdown.md
- **Developed by:** [Plainsource](https://apify.com/fernandosord) (community)
- **Categories:** AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 page converteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

PDF to Text & Markdown downloads the files you list and turns each one into clean text and Markdown, ready for an LLM prompt, a RAG index or a search tool. It converts PDF to Markdown, plus Word, PowerPoint, Excel, EPUB, HTML and images, and runs Tesseract OCR on scanned pages. You get one dataset row per file with the Markdown, tables, metadata and optional chunks.

Tables become Markdown and also CSV files in the run's key-value store. Files that fail to download or convert are not charged.

### What data can PDF to Text & Markdown extract?

| Field | What it holds |
|---|---|
| `markdown` | The document as Markdown, with headings, lists and tables. A row holds at most 1,000,000 characters; longer documents set `markdownTruncated` to `true` and the full text is in `markdownFileUrl`. |
| `pages` | Optional. The Markdown of each page and whether OCR read it. |
| `tables` | Every table with `page`, `rowCount`, `columnCount`, `markdown` and `csvUrl`. |
| `chunks` | Optional. Pieces of N tokens or characters with overlap, each with its heading path and page numbers. |
| `title`, `author`, `createdAt`, `modifiedAt` | Taken from the file when it has them. `metadata` holds the rest, such as `producer`, `creator` and `tokenCount`. |
| `language` | The detected language. |
| `pageCount`, `pagesConverted` | Pages in the file and pages in the output. |
| `ocrUsed`, `ocrPageCount`, `ocrConfidence` | Whether OCR was used, on how many pages, and Tesseract's average confidence from 0 to 100. |
| `characterCount`, `wordCount`, `tableCount` | Counts for the converted text. |
| `status`, `error`, `warnings` | `converted`, `failed`, `not_allowed` or `skipped`, with the reason when it was not converted. |
| `charged` | The events this row was billed for. |

#### Supported formats

| Format | What you get |
|---|---|
| PDF with a text layer | Text in reading order, with two-column layouts read column by column. Headings come from font size and bold numbered titles. Tables with cell borders, and borderless tables whose number columns line up (financial statements, bank statements, statistical releases), become Markdown and CSV. Running headers and page numbers are removed. |
| Scanned PDF | Pages without a usable text layer are rendered at 300 DPI and read with Tesseract. Mixed files are handled page by page. |
| DOCX | Headings, lists, bold and italic, links and tables. |
| PPTX | One section per slide with its title, bullet levels, tables, chart data as a table, and speaker notes. |
| XLSX | One table per sheet with the cached values of formulas. Hidden sheets are marked. |
| EPUB | Chapters in reading order. DRM-protected books are refused. |
| HTML | The main content (`<main>` or the largest `<article>`), without scripts, menus and footers. Links become absolute. |
| PNG, JPG, TIFF, BMP, GIF, WebP | OCR. A multi-page TIFF gives one page per frame. |
| TXT, Markdown, CSV, TSV | Passed through as Markdown; CSV and TSV become a table. |

The file type is read from the file's first bytes, so a PDF served as `application/octet-stream` or without an extension still works. Not supported: password-protected files, legacy `.doc`, `.xls` and `.ppt`, OpenDocument (`.odt`, `.ods`, `.odp`) and RTF. The row says so and you are not charged.

### How to use PDF to Text & Markdown

1. Open the Actor on Apify and add direct links to your files under **Document URLs**. You can also upload a file, pass a dataset whose rows hold file URLs, or point it at a key-value store of files.
2. Leave **OCR mode** on `auto` to run OCR only on pages without a usable text layer. Choose the languages under **OCR languages** if the scans are not in English.
3. Optional: set a **Pages to convert** range such as `1-5, 8, 10-12`, turn on **Include text per page**, or set a **Chunk size for LLM / RAG** to get overlapping chunks.
4. Click **Start**. Each file becomes one row in the dataset.
5. Open the Output tab, or download the dataset as JSON, CSV or Excel. Table CSV files are in the run's key-value store, linked from each row.

### How much does it cost?

The Actor uses pay-per-event pricing, so you pay for documents and pages, not for compute time.

| Event | Price |
|---|---|
| Run start | $0.002 per GB of memory, charged once per run (the default 2048 MB is 2 GB, so $0.004) |
| Document converted | $0.002 per file |
| Page converted from the text layer (PDF page, slide or sheet) | $0.0005 per page |
| Page read with OCR (scanned page or image) | $0.004 per page |

DOCX, EPUB, HTML, TXT, Markdown and CSV files pay only the document fee. Failed, refused and skipped files are free.

Examples at the default memory:

- 1,000 text PDFs of 10 pages each in one run: $0.004 + 1,000 x ($0.002 + 10 x $0.0005) = $7.004.
- One 12-page text PDF in its own run: $0.004 + $0.002 + 12 x $0.0005 = $0.012.
- One 6-page scanned PDF in its own run: $0.004 + $0.002 + 6 x $0.004 = $0.030.
- 100 photos of printed pages in one run: $0.004 + 100 x ($0.002 + $0.004) = $0.604.
- The test run shown below (a 12-page PDF, a 2-page DOCX and a 2-sheet XLSX) was billed 2 start events, 3 documents and 14 pages: $0.017.

If a run reaches the maximum cost you set, the current document stops at the last page that fits, and the rest of the documents are marked `skipped`. The Pricing tab always shows the current prices.

### Input example

Give at least one source: URLs, an uploaded file, a dataset of URLs or a key-value store of files. Every other field is optional. This is the example input from a real run:

```json
{
    "urls": [
        "https://arxiv.org/pdf/1512.03385v1",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.docx",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx"
    ]
}
```

Two PDFs, chunked for a vector database, with page markers:

```json
{
    "urls": [
        "https://arxiv.org/pdf/1810.04805v2",
        "https://www.archives.gov/files/research/jfk/releases/2025/0318/104-10070-10272.pdf"
    ],
    "chunkSize": 800,
    "chunkOverlap": 80,
    "addPageMarkers": true
}
```

The settings you will use most:

| Field | Default | What it does |
|---|---|---|
| `ocrMode` | `auto` | `auto` runs OCR only on pages without a usable text layer and on images. `always` runs OCR on every PDF page. `off` never runs OCR. |
| `ocrLanguages` | `["eng"]` | Any of English, Spanish, French, German, Italian, Portuguese, Dutch, Polish, Turkish, Russian, Ukrainian, Chinese (simplified), Japanese, Korean, Arabic and Hindi. Each extra language slows OCR down. |
| `ocrDpi` | 300 | 100 to 400 DPI for rendering scanned PDF pages. |
| `pageRange` | all | For example `1-5, 8, 10-12`. Applies to PDF pages, PPTX slides, XLSX sheets and TIFF frames. |
| `maxPages` | 500 | Pages after this are skipped, and the row has a warning. Up to 5,000. |
| `pdfTables` | `ruled` | `ruled` finds tables with cell borders and borderless tables whose number columns line up. `auto` also tries tables drawn with horizontal rules only, and sometimes mistakes a diagram for a table. `bordered` finds only tables with cell borders. `off` keeps PDF text as paragraphs. |
| `saveTablesAsCsv` | on | One CSV per table in the key-value store, linked from the row. |
| `removeHeadersFooters` | on | Drops text that repeats at the top or bottom of most PDF pages. Needs 3 or more pages. |
| `includePageText` | off | Adds a `pages` list with each page's Markdown. |
| `addPageMarkers` | off | Adds `<!-- page 3 -->` before each page. |
| `chunkSize`, `chunkUnit`, `chunkOverlap` | 0 (off), `tokens`, 0 | Chunk size of 50 or more, in `tokens` (OpenAI's cl100k_base tokenizer) or `characters`. Overlap is at most half the chunk size. |
| `maxFileSizeMb` | 100 | 1 to 500 MB. Larger files are not downloaded. |
| `documentTimeoutSecs` | 600 | When it runs out, the pages done so far are returned with a warning. |
| `respectRobotsTxt` | on | Skips files that the site's robots.txt does not allow and waits between files from one site as its `Crawl-delay` asks. |

The Input tab in Console lists every field, including `saveMarkdownFile`, `datasetId`, `datasetUrlField`, `keyValueStoreId`, `keyValueStorePrefix` and the download timeout.

### Output example

One row per file. This is the XLSX row from a real run on 2026-10-28, with the input above. The `markdown` string is cut after the first rows and the `tables` list after its first table (the second table is left out); every other value is as returned.

```json
{
    "url": "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx",
    "source": "url",
    "status": "converted",
    "fileName": "test.xlsx",
    "finalUrl": "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx",
    "format": "xlsx",
    "contentType": "application/octet-stream",
    "fileSizeBytes": 11562,
    "title": null,
    "author": "Adam Fourney",
    "createdAt": "2015-06-05T18:17:20Z",
    "modifiedAt": "2024-03-15T05:31:25Z",
    "language": "en",
    "pageCount": 2,
    "pagesConverted": 2,
    "ocrUsed": false,
    "ocrPageCount": 0,
    "ocrConfidence": null,
    "characterCount": 829,
    "wordCount": 134,
    "tableCount": 2,
    "markdown": "## Sheet 1: Sheet1\n\n| Alpha | Beta | Gamma | Delta |\n| --- | --- | --- | --- |\n| 89 | 82 | 100 | 12 |\n| 76 | 89 | 33 | 42 |\n| 60 | 84 | 19 | 19 |\n...",
    "markdownTruncated": false,
    "markdownFileUrl": null,
    "pages": null,
    "pagesFileUrl": null,
    "tables": [
        {
            "index": 1,
            "page": 1,
            "label": "Sheet1",
            "rowCount": 24,
            "columnCount": 4,
            "markdown": "| Alpha | Beta | Gamma | Delta |\n| --- | --- | --- | --- |\n| 89 | 82 | 100 | 12 |\n...",
            "csvUrl": "https://api.apify.com/v2/key-value-stores/NnwGpXCtuE43HYJLD/records/doc-0003-test-table-001.csv?signature=fpLk7T9vylPXbTEzogXH"
        }
    ],
    "chunks": null,
    "chunksFileUrl": null,
    "metadata": {
        "author": "Adam Fourney",
        "lastModifiedBy": "Adam Fourney",
        "createdAt": "2015-06-05T18:17:20Z",
        "modifiedAt": "2024-03-15T05:31:25Z",
        "application": "Microsoft Excel",
        "tokenCount": 471
    },
    "charged": {
        "documents": 1,
        "textPages": 2,
        "ocrPages": 0
    },
    "warnings": [],
    "error": null,
    "secondsTaken": 0.94,
    "convertedAt": "2026-10-28T22:03:31+00:00"
}
```

In the same run the 12-page arXiv PDF gave 16 tables, 10,139 words and a title read from the file's metadata, and the whole run of three files took about 8 seconds.

`status` is one of:

- `converted`: the Markdown and other fields are filled in. Check `warnings` for pages that were skipped or cut short.
- `failed`: the file could not be downloaded or read. The reason is in `error`. Not charged.
- `not_allowed`: the URL points to a private or local address, a site whose terms forbid bots, or a path that robots.txt disallows. Not charged.
- `skipped`: the run reached the maximum cost you set. Not charged.

The run also writes an `OUTPUT` record to its key-value store with counts per status and the number of documents, text pages and OCR pages billed.

#### Borderless tables

Many statements and statistical releases line their columns up with spaces instead of cell borders. With the default `pdfTables` setting these come out as tables too. This is the start of the first table from a test run on the Apify platform with the Federal Reserve's H.4.1 release of October 22, 2026 (page 2, 30 rows and 5 columns; the `csvUrl` in the row links the full CSV):

```
| Reserve Bank credit, related items, and reserve balances of depository institutions at Federal Reserve Banks | Week ended Oct 21, 2026 | Averages of daily figures Change from week ended Oct 14, 2026 | Change from week ended Oct 22, 2025 | Wednesday Oct 21, 2026 |
| --- | --- | --- | --- | --- |
| Reserve Bank credit | 6,706,861 | -789 | +161,908 | 6,706,851 |
| Securities held outright1 | 6,470,860 | +492 | +187,540 | 6,470,178 |
| U.S. Treasury securities | 4,571,248 | +1,337 | +374,817 | 4,572,654 |
```

The table takes the place of the text it covers, so the figures are not repeated as paragraphs. Footnote numbers stay attached to the labels (`outright1`), as printed.

#### Chunks for LLM and RAG

Set `chunkSize` to get a `chunks` list in each row. Each chunk looks like this one, from a local test run on the BERT paper (arXiv 1810.04805) with 150-token chunks and 20 tokens of overlap:

```json
{
    "index": 13,
    "text": "These approaches have been generalized to coarser granularities, such as sentence embeddings (Kiros et al., 2015; Logeswaran and Lee, 2018) or paragraph embeddings (Le and Mikolov, 2014). To train sentence representations, prior work has used objectives to rank candidate next sentences (Jernite et al., 2017; Logeswaran and Lee, 2018), left-to-right generation of next sentence words given a representation of the previous sentence (Kiros et al., 2015), or denoising autoencoder derived objectives (Hill et al., 2016).",
    "characters": 519,
    "tokens": 131,
    "headings": [
        "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding",
        "2 Related Work",
        "2.1 Unsupervised Feature-based Approaches"
    ],
    "pageStart": 2,
    "pageEnd": 2,
    "startOffset": 6075,
    "endOffset": 6594
}
```

Chunks break between paragraphs where they can, then between sentences, then between words. A table that is too big for one chunk is split by rows, and each piece repeats the header row. `pageStart` and `pageEnd` let you cite the source page.

### Use cases

- Prepare PDFs, Word files and slide decks for a RAG pipeline or a vector database, with page numbers and headings kept on every chunk.
- Run OCR on scanned contracts, letters or archive documents and get searchable text.
- Pull financial statements, bank statements and statistical tables out of PDFs as CSV.
- Convert a folder of reports or papers to Markdown for a knowledge base or a documentation site.
- Feed documents to an AI agent as clean text instead of raw PDF bytes.
- Turn spreadsheets and EPUB books into Markdown for search or review.

### FAQ

#### What are the limits?

Files up to 500 MB (`maxFileSizeMb`, default 100 MB) and up to 5,000 pages each (`maxPages`, default 500). One run takes up to 1,000 documents, converted one at a time.

#### How fast is it?

On the default 2 GB of memory, measured on the Apify platform: about 0.5 s per PDF page with a text layer (0.6 to 0.9 s on pages full of number tables) and 6 to 12 s per scanned page with OCR. One 15-megapixel TIFF took 17 s, and a 492-page text PDF took 4 minutes. The 3-file example run above took about 8 seconds. Runs with more memory get more CPU.

#### My scanned PDF came out empty. What should I check?

Check that `ocrMode` is `auto` or `always`. If the scan has a bad text layer from an older OCR run, `always` ignores it and reads the page images.

#### The OCR text has mistakes. Can I improve it?

Pick the right language under `ocrLanguages`. For small print, raise `ocrDpi` to 400. Typewritten and photocopied pages often have errors; `ocrConfidence` shows how sure Tesseract was. OCR reads text, not tables: a scanned table comes out as lines of text.

#### A table came out as paragraphs, or a block of text came out as a table.

Try `pdfTables` set to `auto` for tables drawn with horizontal rules only, or `bordered` to turn off borderless tables. A borderless table is kept only when it has at least 3 rows with numbers, columns that stay in place from row to row, at least one column of numbers and no running text, so tables that hold only words stay as paragraphs. Text in charts and diagrams inside PDFs is left out, and math formulas come out as plain characters in the order the PDF stores them. Please send files that convert badly through the Issues tab.

#### Which sites and addresses are refused?

Only the URLs you give are fetched. There are no proxies, logins or bot-check workarounds; a file behind a login returns `failed` with the HTTP status. LinkedIn, Facebook, Instagram, X and TikTok are refused because their terms forbid automated access. Private and local addresses (such as `localhost`, `10.x.x.x` and cloud metadata addresses) are refused, including through redirects. The Actor identifies itself as `PlainsourceDocToMarkdown` and waits at least 1 s between files from the same site. Office and EPUB files that would unpack to more than 2 GB, or that look like zip bombs, are refused, and so are images over 200 megapixels.

#### Can I send files from my own storage?

Yes. Use **Upload a file**, or put files in a key-value store and set `keyValueStoreId`. Files in your Apify storage skip the robots.txt check.

#### Can I use the API, Make, Zapier, n8n, a schedule or an AI agent?

Yes. Every Apify Actor has an API, and the Make, Zapier and n8n integrations can start it and read the dataset. You can run it on a schedule with Apify's Schedules. AI agents can call it through the Apify MCP server with the same input. For example: `client.actor("fernandosord/document-to-markdown").call({"urls": [...], "chunkSize": 800})`.

#### What does it run on?

Open-source libraries with permissive licences: pdfplumber and pdfminer.six for PDF text and tables, pypdfium2 to render pages, Tesseract for OCR, mammoth for DOCX, python-pptx and openpyxl, markdownify and Beautiful Soup for HTML and EPUB, langdetect and tiktoken. No paid APIs or LLMs are called, and your files are not sent anywhere else.

### Other Actors by this developer

- [Website Screenshot & URL to PDF](https://apify.com/fernandosord/website-screenshot-pdf): full-page screenshots and PDFs of a list of URLs.
- [Website Change Monitor](https://apify.com/fernandosord/website-change-monitor): watch pages and get the exact lines or links that were added or removed.
- [SEC EDGAR API](https://apify.com/fernandosord/sec-edgar-api): filings, XBRL financials, insider trades and 10-K sections as text.
- [Website Tech Stack Detector](https://apify.com/fernandosord/website-tech-stack-detector): find out which CMS, framework and tools a site uses.
- [Career Site Jobs](https://apify.com/fernandosord/career-site-jobs): open jobs from Greenhouse, Lever, Ashby and other career sites.

# Actor input Schema

## `urls` (type: `array`):

Direct links to the files to convert: PDF, DOCX, PPTX, XLSX, EPUB, HTML, TXT, Markdown, CSV, or images (PNG, JPG, TIFF, BMP, GIF, WebP). Each file becomes one row in the dataset. Only these URLs are fetched; links inside the files are not followed.

## `file` (type: `string`):

Upload one file from your computer instead of (or as well as) giving URLs. It is stored in your Apify account and converted like any other URL.

## `datasetId` (type: `string`):

ID of an Apify dataset whose rows hold file URLs, for example the output of a scraper that collected PDF links. Up to 1,000 rows are read.

## `datasetUrlField` (type: `string`):

The field in each dataset row that holds the file URL.

## `keyValueStoreId` (type: `string`):

ID of an Apify key-value store. Every record in it is converted as a file (INPUT and OUTPUT are skipped).

## `keyValueStorePrefix` (type: `string`):

Convert only the key-value store records whose key starts with this text.

## `ocrMode` (type: `string`):

<b>Auto</b> runs OCR only on PDF pages without a usable text layer (scans and photos) and on images. <b>Always</b> runs OCR on every PDF page, which is slower and charged as OCR pages. <b>Off</b> never runs OCR; images then fail and scanned pages come out empty.

## `ocrLanguages` (type: `array`):

Languages Tesseract looks for. Pick the languages of your scans; each extra language makes OCR slower. This setting does not affect files that already have a text layer.

## `ocrDpi` (type: `integer`):

Resolution used to render scanned PDF pages for OCR. 300 suits most scans; 200 is faster; go higher only for very small print.

## `pageRange` (type: `string`):

Only these pages, slides or sheets, for example <code>1-5, 8, 10-12</code>. Leave empty for all. Applies to PDFs, PPTX slides, XLSX sheets and multi-page TIFFs.

## `maxPages` (type: `integer`):

Pages (or slides, sheets, TIFF frames) after this many are not converted, so a 900-page file can't use up your budget. The row says when this happened.

## `pdfTables` (type: `string`):

<b>Standard</b> finds tables drawn with cell borders and borderless tables whose number columns line up with white space, as in financial statements, bank statements and statistical releases. Each borderless table must pass checks (at least 3 rows with numbers, columns that stay in place, no running text), so ordinary paragraphs are left alone. <b>Standard plus rule-only tables</b> also tries tables that only have horizontal rules (common in papers); it finds more tables but sometimes mistakes a chart or diagram for one. <b>Bordered tables only</b> is the behaviour before October 2026. <b>Off</b> keeps all PDF text as paragraphs. DOCX, PPTX, XLSX and HTML tables are always extracted.

## `saveTablesAsCsv` (type: `boolean`):

Save every table as a CSV file in the run's key-value store and add its link to the row. Tables are always in the Markdown too.

## `removeHeadersFooters` (type: `boolean`):

In PDFs, drop lines that repeat at the top or bottom of most pages (running titles, page numbers). Needs at least 3 pages.

## `includePageText` (type: `boolean`):

Add a <code>pages</code> list with the Markdown of each page, slide or sheet, and whether it was read with OCR.

## `addPageMarkers` (type: `boolean`):

Put an HTML comment such as <code>\<!-- page 3 --></code> before each page in the Markdown, so you can cite pages later.

## `saveMarkdownFile` (type: `string`):

Also save the full Markdown as a .md file in the key-value store and link it in <code>markdownFileUrl</code>. <b>Auto</b> does this for documents over 100,000 characters. Markdown over 1,000,000 characters is always saved as a file, because the dataset row only holds the first 1,000,000.

## `chunkSize` (type: `integer`):

Split the Markdown into chunks of at most this size and add them as <code>chunks</code>, with headings and page numbers for each chunk. 0 turns chunking off. 500 to 1,000 tokens is a common choice for retrieval.

## `chunkUnit` (type: `string`):

Count chunk size and overlap in tokens (OpenAI's cl100k_base tokenizer) or in characters.

## `chunkOverlap` (type: `integer`):

How much of the end of each chunk is repeated at the start of the next, in the unit above. At most half the chunk size.

## `maxFileSizeMb` (type: `integer`):

Files larger than this are not downloaded and the row says why. Not charged.

## `downloadTimeoutSecs` (type: `integer`):

A download that takes longer than this is stopped and the file is marked as failed (not charged).

## `documentTimeoutSecs` (type: `integer`):

When converting one file takes longer than this, the pages finished so far are returned with a warning, and only they are charged. A file that hangs is stopped 30 s later.

## `respectRobotsTxt` (type: `boolean`):

Skip files that the site's robots.txt does not allow bots to fetch, and wait between files from the same site as its Crawl-delay asks. Turn off only for files on sites you own or have permission to fetch. Files in your own Apify storage are always allowed.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1512.03385v1",
    "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.docx",
    "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx"
  ],
  "datasetUrlField": "url",
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "ocrDpi": 300,
  "maxPages": 500,
  "pdfTables": "ruled",
  "saveTablesAsCsv": true,
  "removeHeadersFooters": true,
  "includePageText": false,
  "addPageMarkers": false,
  "saveMarkdownFile": "auto",
  "chunkSize": 0,
  "chunkUnit": "tokens",
  "chunkOverlap": 0,
  "maxFileSizeMb": 100,
  "downloadTimeoutSecs": 120,
  "documentTimeoutSecs": 600,
  "respectRobotsTxt": true
}
```

# Actor output Schema

## `documents` (type: `string`):

One row per file: Markdown, metadata, page count, OCR use, tables with CSV links, optional per-page text and chunks, or the reason the file failed.

## `files` (type: `string`):

The key-value store with each table as CSV and the full Markdown of large documents.

## `summary` (type: `string`):

Counts per status and the number of documents, text pages and OCR pages billed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://arxiv.org/pdf/1512.03385v1",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.docx",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fernandosord/document-to-markdown").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://arxiv.org/pdf/1512.03385v1",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.docx",
        "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("fernandosord/document-to-markdown").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://arxiv.org/pdf/1512.03385v1",
    "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.docx",
    "https://raw.githubusercontent.com/microsoft/markitdown/7482c092d9c0074a89803c9fdb63a969e7fe17fc/packages/markitdown/tests/test_files/test.xlsx"
  ]
}' |
apify call fernandosord/document-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fernandosord/document-to-markdown"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BBBE205TR6LizAfut/builds/vaLi6ULaBr94pliT6/openapi.json
