# Doc to Markdown for AI Agents — PDF, Word, PowerPoint, Excel (`sourcewire/doc-to-markdown`) Actor

Convert PDF, DOCX, PPTX, XLSX and HTML links into clean, LLM-ready Markdown with tables preserved, metadata and RAG-ready chunks. Predictable JSON, scanned-PDF detection, and failed files are never charged. Built for AI agents, MCP and RAG.

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

## Pricing

from $5.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

## Doc to Markdown for AI Agents — PDF, Word, PowerPoint & Excel to clean Markdown

*By **SourceWire** — clean, agent-ready data tools.*

Turn any public **PDF, DOCX, PPTX, XLSX/XLS, HTML, CSV or TXT** link into clean, LLM-ready **Markdown** — with **tables preserved**, document **metadata**, and **RAG-ready chunks** in one predictable JSON record per file. Built for AI agents, RAG pipelines and LLM apps that need the *content* of a document, not a pile of broken text.

**You only pay for documents that convert.** Bad links, files that are too large, unsupported formats, password-protected files and scanned PDFs without a text layer are reported with a clear error code — and are **never charged**.

### Why use it

- **Tables that stay tables.** Ruled tables *and* borderless "booktabs" tables (research papers, financial reports) are rebuilt as GitHub-flavoured Markdown tables. Excel sheets keep multi-row headers, and Word tables keep their header row.
- **Correct reading order.** Multi-column PDFs (papers, brochures, IRS publications) are read column by column, not line-interleaved. Running headers, footers, crop marks and rotated side stamps are removed.
- **Real headings.** Font-size analysis turns PDF titles into `#`/`##`/`###` headings, so chunks carry a meaningful `headingPath`.
- **RAG chunks built in.** Heading-aware chunks of a size you pick (default ~512 tokens, 50 overlap). Tables are never cut mid-row; long tables are split by rows with the header repeated. Every chunk has `page`, `headingPath`, `tokenEstimate`.
- **One predictable schema.** The same fields for every format and every outcome: `status`, `errorCode`, `title`, `pageCount`, `wordCount`, `estimatedTokens`, `tableCount`, `markdown`, `chunks`. No guessing in your agent code.
- **Reliable at scale.** Each document is parsed in an isolated process with a timeout, so one broken file can't stall or crash your run. File size is checked before download completes.
- **Share links just work.** Google Docs / Sheets / Slides, Google Drive, Dropbox and GitHub file links are converted to direct downloads automatically (the file must be shared publicly).
- **Private by design.** Files are processed in memory/temp storage for the run and deleted right after conversion.

### Use cases

- **RAG ingestion:** feed PDFs, manuals, policies and slide decks straight into a vector database using the `chunks` array.
- **AI agents & MCP tools:** give Claude, ChatGPT, Cursor or your own agent a "read this document" tool that returns compact Markdown instead of raw bytes.
- **Spreadsheet-to-LLM:** convert Excel reports into Markdown tables an LLM can actually reason over.
- **Research & due diligence:** turn filings, papers and reports into searchable text with page numbers for citations (`includePages`).
- **Knowledge-base migration:** bulk-convert Word and PowerPoint libraries into Markdown for docs sites, wikis or Notion-style tools.
- **Change monitoring:** use `sha256` to skip documents that haven't changed since the last run.

### Supported formats

| Format | What you get |
| --- | --- |
| PDF (text-based) | Headings, paragraphs in reading order, ruled + borderless tables, per-page output, metadata |
| Word (.docx) | Headings, lists, bold/italic, links, tables, metadata, page count |
| PowerPoint (.pptx) | One section per slide (`## Slide N`), text, tables, speaker notes |
| Excel (.xlsx, .xls) | One Markdown table per sheet with merged multi-row headers, title/notes rows kept as text |
| HTML pages | Main content only (navigation, menus, footers and scripts removed), absolute links |
| CSV, TXT, Markdown | Clean Markdown / Markdown table |

Scanned PDFs (images only) are detected and returned with `status: "needs_ocr"` — not charged. Partly scanned PDFs are converted and the image-only pages are listed in `scannedPages`.

### Input

| Field | Default | Description |
| --- | --- | --- |
| `urls` | — | List of public document links (required) |
| `chunkStrategy` | `size` | `size` (heading-aware, recommended), `section`, `page` or `none` |
| `chunkSize` | `512` | Target chunk size in estimated tokens |
| `chunkOverlap` | `50` | Overlap between `size` chunks (never inside tables) |
| `includeMarkdown` | `true` | Return the full document Markdown |
| `includePages` | `false` | Also return Markdown per PDF page / slide / sheet |
| `detectTables` | `true` | Rebuild PDF tables as Markdown tables |
| `saveMarkdownFile` | `false` | Also save a `.md` file and return its link |
| `maxPages` | `500` | Max pages converted per PDF |
| `maxFileSizeMb` | `50` | Larger files are rejected (not charged) |
| `maxDocuments` | `100` | Max URLs per run |
| `timeoutSecs` | `180` | Per-document conversion timeout |
| `maxConcurrency` | `2` | Documents converted in parallel |

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://docs.google.com/document/d/YOUR_PUBLIC_DOC_ID/edit"
  ],
  "chunkStrategy": "size",
  "chunkSize": 512,
  "chunkOverlap": 50
}
```

### Output

One dataset item per URL:

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "status": "ok",
  "success": true,
  "errorCode": null,
  "format": "pdf",
  "title": "Form W-9 (Rev. March 2024)",
  "pageCount": 6,
  "pagesConverted": 6,
  "pageLabel": "page",
  "wordCount": 6370,
  "estimatedTokens": 9491,
  "tableCount": 2,
  "markdown": "# W-9\n\n# Request for Taxpayer\n\n...",
  "chunks": [
    {
      "index": 0,
      "text": "# W-9\n\n# Request for Taxpayer ...",
      "page": 1,
      "pageEnd": 1,
      "headingPath": "W-9 > Request for Taxpayer",
      "hasTable": false,
      "charCount": 1890,
      "tokenEstimate": 472
    }
  ],
  "scannedPages": [],
  "warnings": [],
  "charged": true,
  "billedUnits": 1
}
```

Failed items use the same shape with `status: "error"` (or `needs_ocr`), an `errorCode` such as `NOT_FOUND`, `ACCESS_DENIED`, `FILE_TOO_LARGE`, `UNSUPPORTED_FORMAT`, `ENCRYPTED_PDF`, `SCANNED_PDF` or `TIMEOUT`, a readable `errorMessage`, and `charged: false`.

Very large documents that don't fit in one dataset item are saved as a `.md` file in the run's key-value store and returned as `markdownUrl`.

### Pricing

Pay per event — **`document-converted`**: one event per successfully converted document. Long documents count one extra event per additional 50 PDF pages or slides (or per 25,000 words for other formats), so a 10-page PDF is 1 event and a 120-page PDF is 3. Failed, scanned and skipped documents are free. Set a maximum cost per run in the Console and the Actor stops cleanly when it's reached.

### For AI agents (MCP)

Use this Actor as a tool in any MCP client (Claude Desktop, Cursor, VS Code, ChatGPT connectors and others) through the Apify MCP server:

```
https://mcp.apify.com/?actors=SourceWire/doc-to-markdown
```

Tips for agents:

- Pass one or more `urls`; read `markdown` for a quick answer or `chunks` for retrieval.
- Check `status` first: `ok`, `error` (see `errorCode`), or `needs_ocr`.
- Use `estimatedTokens` to decide whether to summarise or chunk before putting the document in context.
- Use `chunkStrategy: "none"` and `includeMarkdown: true` for the smallest response.

You can also call it from code with the Apify API or client libraries (Python, JavaScript) and from Make, Zapier, n8n and LangChain/LlamaIndex Apify integrations.

### Privacy

Only the URLs you submit are downloaded. Files are kept in temporary storage for the duration of the conversion and deleted immediately after. Private or internal network addresses are blocked. The Actor does not log in to any site — links must be publicly accessible.

### Limitations

- No OCR: image-only (scanned) PDF pages are detected and flagged, not read.
- Legacy binary `.doc` and `.ppt` files and password-protected files are not supported — re-save as `.docx` / `.pptx`.
- Charts and images are not described; images are dropped (alt text is kept for HTML/Word where available).
- Very complex PDF tables (nested headers, cells spanning many rows) may need light clean-up.
- Pages behind logins, paywalls or bot protection can't be fetched.

*SourceWire builds focused, reliable data tools for AI agents and automation. Questions or a format that doesn't convert well? Open an issue on the Actor's Issues tab — we read every one.*

# Actor input Schema

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

Public links to PDF, DOCX, PPTX, XLSX/XLS, HTML, CSV, TXT or MD files. Google Docs/Sheets/Slides share links, Google Drive file links, Dropbox and GitHub 'blob' links are converted to direct downloads automatically (the file must be shared publicly).

## `chunkStrategy` (type: `string`):

How to split each document into chunks for embeddings. 'size' = heading-aware chunks of about chunkSize tokens (recommended). 'section' = one chunk per heading section. 'page' = one chunk per page / slide / sheet. 'none' = no chunks.

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

Target maximum chunk size in estimated tokens (≈4 characters each). Used by 'size' and 'section' strategies. Tables are never split mid-row; long tables are split by rows with the header repeated.

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

Text carried over between consecutive 'size' chunks for context. Never applied inside tables.

## `includeMarkdown` (type: `boolean`):

Return the whole document as one 'markdown' field.

## `includePages` (type: `boolean`):

Also return a 'pages' array with Markdown per PDF page, slide or sheet (useful for page-level citations).

## `detectTables` (type: `boolean`):

Rebuild ruled and booktabs-style PDF tables as Markdown tables. Turn off for slightly faster text-only output.

## `saveMarkdownFile` (type: `boolean`):

Save each document's Markdown as a file in the key-value store and return its link in 'markdownUrl'. Very large documents are always saved this way automatically.

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

Convert at most this many pages of each PDF (the rest is skipped with a warning). Billing counts converted pages only.

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

Files larger than this are rejected before conversion (not charged).

## `maxDocuments` (type: `integer`):

Safety cap on how many URLs are processed in one run.

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

A document that takes longer than this to convert is stopped and reported as TIMEOUT (not charged).

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

How many documents to convert at the same time. 2 is ideal for 1 GB memory; raise it together with memory.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ],
  "chunkStrategy": "size",
  "chunkSize": 512,
  "chunkOverlap": 50,
  "includeMarkdown": true,
  "includePages": false,
  "detectTables": true,
  "saveMarkdownFile": false,
  "maxPages": 500,
  "maxFileSizeMb": 50,
  "maxDocuments": 100,
  "timeoutSecs": 180,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

No description

# 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://www.irs.gov/pub/irs-pdf/fw9.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sourcewire/doc-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://www.irs.gov/pub/irs-pdf/fw9.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("sourcewire/doc-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://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ]
}' |
apify call sourcewire/doc-to-markdown --silent --output-dataset

```

## MCP server setup

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