# Document to Markdown: PDF, DOCX, XLSX for LLMs & RAG (`catalyst-labs/document-to-markdown`) Actor

PDF, Word, Excel, CSV and HTML to clean markdown for agents and RAG: tables kept, PDF page markers, optional chunks with heading and page. $0.001 per page; failed or scanned files never charged. One row per document with status and error reason.

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

## Pricing

from $1.00 / 1,000 pages

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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: PDF, DOCX, XLSX, CSV, HTML to LLM-ready markdown

### Why agents pick this

- **$0.001 per page.** A 15-page PDF costs $0.016. Failed, scanned-only (`NO_TEXT_LAYER`) and password-protected files are never charged.
- **Tables survive.** DOCX and XLSX tables come out as GFM tables, not flattened text, and PDFs get `<!-- page N -->` markers so you can cite pages.
- **RAG-ready chunks** (optional), each with its heading and page number.
- **File type is detected from the bytes**, not the URL, so links without a file extension still work. `maxChars` caps tokens, and you pay only for what is returned.

**What it does:** turns documents at public URLs (or inline base64 files) into clean **markdown**: headings, lists and tables kept, PDF page numbers marked, optional **RAG chunks** with heading and page per chunk. One flat JSON row per document with `status`, source `url`, page counts and, on failure, `errorCode` + `errorMessage`.

**Use it when** an agent needs to: read a PDF report, contract, filing or paper; turn a Word or Excel file into text it can reason over; or chunk documents for a vector store.

**Don't use it for:** scanned/image-only PDFs (returns `NO_TEXT_LAYER`, not charged: OCR is not included), password-protected files, PowerPoint (not yet), web search, or reading normal web pages (use `catalyst-labs/agent-web-reader`).

### Cost per call

| Event | Price | When |
|---|---|---|
| `page` | $0.001 | per PDF page converted; other formats count 1 page per 3,000 characters returned |
| `actor-start` | $0.001 | once per batch run (not charged in Standby/HTTP mode) |

Examples: a 15-page PDF = **$0.016**; a short DOCX = **$0.002**. Failed documents are **never charged**, and with `maxChars` you only pay for what is returned. Use `maxTotalChargeUsd` to cap a run.

### Input (only `urls` or `files` is required)

```json
{ "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"] }
```

```json
{ "urls": ["https://arxiv.org/pdf/1706.03762"], "pages": "1-5", "chunkSize": 1500, "chunkOverlap": 150 }
```

| Field | Default | Notes |
|---|---|---|
| `urls` | – | Up to 100 links. Type detected from file bytes. |
| `files` | – | `[{"name":"a.docx","base64":"..."}]` for agents that hold the bytes (input limit ~9 MB). |
| `maxChars` | 50000 | Token budget per document; 0 = unlimited. |
| `pages` | all | PDF range: `1-10`, `5`, `20-`. |
| `maxPages` | 200 | PDF safety cap. |
| `chunkSize` | 0 (off) | Chunk size in characters for RAG. |
| `chunkOverlap` | 200 | Overlap between chunks. |
| `includeFullMarkdown` | false | Also return `markdown` when chunking. |
| `pageMarkers` | true | `<!-- page N -->` before each PDF page. |
| `keepLinks` | false | Keep hyperlinks. |
| `maxRowsPerSheet` | 500 | Spreadsheet rows per sheet. |

### Output

```json
{
  "status": "ok", "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "fileName": "fw9.pdf",
  "docType": "pdf", "title": "Form W-9 (Rev. March 2024)", "pageCount": 6, "pagesProcessed": 6, "billedPages": 6,
  "markdown": "<!-- page 1 -->\n\n#### Request for Taxpayer Identification Number ...",
  "chars": 21000, "totalChars": 21000, "truncated": false,
  "chunks": null, "chunkCount": 0, "fetchedAt": "2026-10-25T05:00:00.000Z", "ms": 900,
  "errorCode": null, "errorMessage": null
}
```

With chunking, `chunks` is `[{"index":0,"heading":"Revenue","page":3,"text":"...","chars":1480}]`.

Error codes: `INVALID_URL`, `INVALID_FILE`, `DNS_NOT_FOUND`, `TIMEOUT`, `HTTP_4xx`/`HTTP_5xx`, `TOO_LARGE` (>50 MB), `NO_TEXT_LAYER`, `ENCRYPTED`, `UNSUPPORTED_TYPE`, `EMPTY_CONTENT`, `PARSE_FAILED`, `SPENDING_LIMIT`.

### Formats and quality notes

- **DOCX:** headings, lists, bold/italic and tables become markdown (tables as GFM tables).
- **XLSX/XLS/CSV:** each sheet becomes a markdown table (first row as header).
- **HTML/TXT/MD:** main content extracted / passed through.
- **PDF:** text per page in reading order; headings guessed from font size. PDF tables come out as text lines, not markdown tables (honest limitation).

### Fast calls: Standby (HTTP) mode

```bash
curl "https://catalyst-labs--document-to-markdown.apify.actor/?urls=https://www.irs.gov/pub/irs-pdf/fw9.pdf&maxChars=4000&token=$APIFY_TOKEN"
```

### Use from MCP

`https://mcp.apify.com/?tools=catalyst-labs/document-to-markdown` → tool `catalyst-labs--document-to-markdown`.

Deterministic: the same file and options give the same output. Files are processed in memory and not stored beyond the run's dataset.

Made by Catalyst Labs. Also see: Agent Web Reader, Google Trends API, YouTube Transcript API.

# Actor input Schema

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

Public links to PDF, DOCX, XLSX, XLS, CSV, HTML, TXT or MD files (max 100). Type is detected from the file bytes, not the extension.

## `files` (type: `array`):

Optional inline files for agents that hold the bytes: `[{"name": "report.pdf", "base64": "..."}]`. Keep under ~9 MB total (input size limit).

## `maxChars` (type: `integer`):

Token budget per document (about 4 characters = 1 token). Cut at a paragraph boundary; 0 = no limit. You pay only for the pages actually returned. Default 50000.

## `pages` (type: `string`):

PDF only: pages to convert, e.g. `1-10`, `5`, `20-`. Default all (up to maxPages).

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

Safety cap on PDF pages per document. Default 200.

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

For RAG: split each document into chunks of about this many characters on heading/paragraph boundaries, each with its heading and page number. 0 = off (default).

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

Characters repeated between consecutive chunks. Default 200.

## `includeFullMarkdown` (type: `boolean`):

When chunking, also return the whole `markdown` field (doubles output size). Off by default.

## `pageMarkers` (type: `boolean`):

Insert `<!-- page N -->` before each PDF page so citations can point at a page.

## `keepLinks` (type: `boolean`):

Keep `[text](url)` links from DOCX/HTML. Off saves tokens.

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

Spreadsheets: rows per sheet rendered as a markdown table. Default 500.

## `timeoutSecs` (type: `integer`):

Per-document download timeout. Default 60.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "maxChars": 50000,
  "maxPages": 200,
  "chunkSize": 0,
  "chunkOverlap": 200,
  "includeFullMarkdown": false,
  "pageMarkers": true,
  "keepLinks": false,
  "maxRowsPerSheet": 500,
  "timeoutSecs": 60
}
```

# Actor output Schema

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

One row per document.

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

okCount, errorCount, chargedEvents and all rows.

# 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("catalyst-labs/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("catalyst-labs/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 catalyst-labs/document-to-markdown --silent --output-dataset

```

## MCP server setup

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