# Document to Markdown Converter for RAG (`ottomath/document-to-markdown`) Actor

Convert PDF, DOCX, XLSX, PPTX, HTML and EPUB links to clean Markdown or RAG-ready chunks. No OCR, no browser. $4 per 1,000 documents plus page fees.

- **URL**: https://apify.com/ottomath/document-to-markdown.md
- **Developed by:** [Ottomath](https://apify.com/ottomath) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 document 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

## Document to Markdown Converter

Turn PDF, DOCX, XLSX, XLS, CSV, PPTX, HTML and EPUB files from URLs into clean Markdown for AI agents, RAG pipelines and search indexes. Headings, lists, tables and links are kept, pages, slides and sheets stay marked, and the output can be split into chunks that are ready for embedding. Plain HTTP and local parsing only: no browser, no OCR, no proxies and no AI model.

### What it does

For every URL in your list the Actor downloads the file (50 MB by default), detects the format, converts it to Markdown and stores one dataset item per document, or one item per chunk. Detection looks at the file content first (magic bytes), then the content type, then the file name, so a PDF served as `download.php` is still a PDF.

| Format | What you get |
| --- | --- |
| PDF | The text layer, page by page, with `<!-- page N -->` markers. Headings come from font sizes, tables from aligned columns. Lists, links, two-column reading order and de-hyphenation are handled, repeated page headers and footers are removed. |
| DOCX | Headings, paragraphs, bold and italic text, lists, tables and links. |
| XLSX, XLS, CSV | One heading and one Markdown table per visible sheet, with a row cap. A CSV file counts as one sheet. |
| PPTX | One section per slide in presentation order: title, bullet text, tables, links and, if you want them, the speaker notes. |
| HTML | The main content only. Navigation, footers, cookie banners and scripts are dropped, headings, lists, tables and code are kept and links become absolute. |
| EPUB | The chapters in reading order, title from the book metadata. |
| Markdown, plain text | Passed through after the character encoding is detected. |

**Chunks for embeddings.** With `outputMode` set to `chunks` each document becomes several items of at most `chunkSize` characters. Text is cut at headings first, then at paragraphs, then at sentences and words. Every chunk carries the heading path (`section`) and, for PDFs, the `page`. Tables are split by rows with the header repeated, code blocks stay fenced, and the overlap is added only when a section has to be cut in the middle.

**Safe by design.** Files are parsed in worker threads with a time limit and a memory guard, so one broken file cannot stop a run. ZIP-based formats are protected against zip bombs (limits on entries and on the real decompressed size). Macros are never executed, and nothing a document refers to (images, remote templates, external links) is ever downloaded.

### Use cases

- **RAG and vector search:** turn manuals, reports and papers into chunks with section and page metadata, then embed them.
- **AI agents:** give an agent a readable version of any document URL instead of raw bytes.
- **Knowledge base migration:** move PDFs, slide decks and spreadsheets into a Markdown wiki or docs-as-code repository.
- **Research and monitoring:** convert public papers, standards and government reports on a schedule and diff the text.
- **Data hand-over to LLMs:** present spreadsheet sheets as Markdown tables that language models read well.

### Input

| Field | Default | Description |
| --- | --- | --- |
| `urls` | required | Document URLs, up to 1,000. Duplicates and `#fragments` are converted once. |
| `outputMode` | `document` | `document` gives one item per document, `chunks` one item per chunk. |
| `chunkSize` | 2000 | Maximum characters per chunk (200 to 20,000). |
| `chunkOverlap` | 200 | Characters repeated when a section is split; at most half of `chunkSize`. |
| `maxPages` | 500 | Maximum PDF pages, PPTX slides or sheets per document (1 to 5,000). Larger files are cut with a warning and only converted units are charged. |
| `includeSlideNotes` | true | Add PPTX speaker notes under each slide. |
| `maxRowsPerSheet` | 1000 | Row cap per sheet or CSV file (1 to 100,000). |
| `respectRobotsTxt` | true | Download only what the host's robots.txt allows this Actor. |
| `maxFileSizeMb` | 50 | Larger files are not converted (1 to 100). |
| `requestTimeoutSecs` | 60 | Download timeout (1 to 300). |
| `maxConcurrency` | 5 | Documents downloaded in parallel (1 to 20). At most 2 conversions run at once. |

```json
{
  "urls": ["https://arxiv.org/pdf/1706.03762"],
  "outputMode": "chunks",
  "chunkSize": 1500,
  "chunkOverlap": 150
}
```

### Output example

One dataset item per document. A failed document gets an item with `error` set and costs nothing. This is a shortened result of a real run (the Markdown is cut):

```json
{
  "url": "https://arxiv.org/pdf/1706.03762",
  "finalUrl": "https://arxiv.org/pdf/1706.03762",
  "format": "pdf",
  "title": "Attention Is All You Need",
  "pageCount": 15,
  "slideCount": null,
  "sheetCount": null,
  "wordCount": 5743,
  "markdown": "<!-- page 1 -->\n\nProvided proper attribution is provided, Google hereby grants permission to ...\n\n# Attention Is All You Need\n\n| Ashish Vaswani∗ | Noam Shazeer∗ | ... |\n\n## Abstract\n\nThe dominant sequence transduction models are based on ...",
  "warnings": ["Some rotated text (for example margin stamps or sideways labels) was skipped"],
  "convertedAt": "2026-10-09T01:31:17.832Z",
  "error": null
}
```

In chunks mode the `markdown` field is replaced by `chunkIndex`, `chunkCount`, `text`, `section` and `page`, and the document fields (`url`, `format`, `title`, page, slide or sheet count) are repeated on every chunk; `wordCount` then counts that chunk. The Console table shows URL, format, title, pages, words and error.

### Pricing

Pay per event. You pay for documents that were converted, never for failures.

| Event | Price | When it is charged |
| --- | --- | --- |
| Actor start | $0.005 | Once per run. |
| Document converted | $0.004 | Once per document that produced Markdown, whatever its size or format. |
| Extra page, slide or sheet converted | $0.0005 | For each PDF page with text, PPTX slide or spreadsheet sheet after the first one. |

Invalid URLs, HTTP errors, robots.txt blocks, files that are too large, unsupported or password-protected files, scanned PDFs without a text layer, empty documents and timeouts are free. Examples: 100 one-page documents cost about $0.41, one 15-page paper $0.016, and 1,000 documents of 10 pages on average about $8.51. The Actor never charges beyond the maximum total charge you set; it stops early and says so in the run status.

### FAQ

**Does it run OCR?** No. A PDF that contains only scanned images has no text layer and returns the error "No text layer (OCR not supported)" at no charge.

**Does it respect robots.txt?** Yes. With `respectRobotsTxt` on, the Actor reads the host's robots.txt and downloads a document only if the file allows its own user agent, `OttomathDocToMarkdown/1.0 (+https://apify.com)`, or `*`. If robots.txt cannot be read because of a server error, the document is skipped and not charged. It uses no proxies and does not pretend to be a browser.

**How are pages counted?** PDF pages that contain text, PPTX slides and spreadsheet sheets. Blank PDF pages are skipped and not charged. All other formats only pay the document event.

**Can it open files behind a login?** No. There is no way to send cookies or headers. Use publicly reachable URLs, for example presigned links.

**Is my data stored somewhere else?** No. Documents are downloaded into memory, converted and discarded. Results go to the run's dataset only. The Actor does not look for personal data and does not call any external service other than the document hosts.

**What if a document is very large?** Conversion stops after about 100 seconds with the pages read so far and a warning; after 120 seconds it is abandoned with a free error. Use `maxPages` to bound the work. A result larger than about 7.5 MB of Markdown is cut at a paragraph boundary with a warning.

**Why are some words split or joined oddly?** PDFs store positioned glyphs, not paragraphs. The Actor rebuilds lines and paragraphs from positions, which works well for ordinary text and is a best effort for unusual layouts.

### Limitations

- No OCR, no image or chart recognition. Pictures, charts, drawings and SmartArt are not converted or described.
- PDF layout is reconstructed from text positions. Bold and italic are not detected, three or more columns are not separated, multi-line table cells can be split over several rows, formulas come out as plain text and rotated text is skipped.
- Legacy `.doc`, `.ppt` and `.rtf`, OpenDocument, JSON and XML files are not supported and return a free error. Password-protected files cannot be opened.
- Spreadsheets: hidden sheets are skipped, formulas are not evaluated (the stored values are shown) and long sheets are cut at `maxRowsPerSheet`.
- Limits: 1,000 URLs per run, 50 MB per file by default (100 MB at most), 7.5 MB of Markdown per item, 120 seconds per document.
- Hosts that block data-centre traffic answer with errors such as 403; those documents are free.
- The Actor is not affiliated with any document format owner or software vendor.

### Changelog

- **1.0 (October 2026):** first release.

# Actor input Schema

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

One http(s) URL per line: PDF, DOCX, XLSX, XLS, CSV, PPTX, HTML, EPUB, Markdown or plain text. The format is detected from the content, not only from the file name. Up to 1,000 URLs per run. Duplicates are converted once.

## `outputMode` (type: `string`):

document: one dataset item per document with the full Markdown. chunks: one item per chunk, split on headings and paragraphs first, ready for embedding.

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

Maximum length of a chunk, in characters. Only used when the output mode is chunks.

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

Characters repeated at the start of the next chunk when a section has to be split in the middle. Chunks that start at a new heading start clean. Must be at most half of the chunk size.

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

Convert at most this many PDF pages, PPTX slides or spreadsheet sheets per document. Larger documents are cut off with a warning, and only the converted units are charged.

## `includeSlideNotes` (type: `boolean`):

Add the speaker notes of each PPTX slide under the slide content.

## `maxRowsPerSheet` (type: `integer`):

Row cap for each spreadsheet sheet and CSV file. Longer sheets are cut off with a warning.

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

Read the robots.txt of each host and download only documents it allows for this Actor's own User-Agent (or `*`). Blocked documents are reported with an error and are free. Turn off only for hosts you own or may fetch from.

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

Documents larger than this are not downloaded or converted (error, free).

## `requestTimeoutSecs` (type: `integer`):

Give up on a download after this many seconds (headers and body).

## `maxConcurrency` (type: `integer`):

How many documents are downloaded in parallel. Conversion itself runs in at most 2 worker threads, and at most 2 requests per host run at the same time.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "outputMode": "document",
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "maxPages": 500,
  "includeSlideNotes": true,
  "maxRowsPerSheet": 1000,
  "respectRobotsTxt": true,
  "maxFileSizeMb": 50,
  "requestTimeoutSecs": 60,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `results` (type: `string`):

One item per document (or per chunk in chunks mode) with the Markdown and its metadata (and free error rows), as JSON. Use the dataset tabs in the Console for tables, CSV and Excel exports.

# 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/1706.03762"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ottomath/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/1706.03762"] }

# Run the Actor and wait for it to finish
run = client.actor("ottomath/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/1706.03762"
  ]
}' |
apify call ottomath/document-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ottomath/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/sTKgCgijnGUWolkIU/builds/aTjl7WCsK5mYvgzqL/openapi.json
