# PDF to Markdown & JSON for RAG (OCR, DOCX, PPTX, XLSX) (`maxatron/docmd`) Actor

Convert PDF, DOCX, PPTX, XLSX and HTML into clean Markdown and JSON for AI and RAG pipelines. Fast mode is cheap and near-instant; accurate mode adds full layout and table analysis. OCR for scanned PDFs, tables as Markdown/HTML/JSON, page or token chunking with token counts.

- **URL**: https://apify.com/maxatron/docmd.md
- **Developed by:** [Maximillian Ruddy](https://apify.com/maxatron) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 converted pages

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

## docmd: documents to Markdown and JSON for RAG

Convert **PDF (text or scanned), DOCX, PPTX, XLSX and HTML** documents at URLs into clean
**Markdown** and **structured JSON**, ready to feed into an LLM, a vector database or a RAG pipeline.

- **Fast by default:** a normal text PDF page converts in about half a second.
- **Accurate mode when you need it:** full layout analysis and table-structure recognition for
  dense forms and complex tables.
- **OCR built in:** scanned pages are detected and read automatically.
- **Tables two ways:** inline in the Markdown, and as a separate structured array (Markdown,
  HTML or JSON cells).
- **Chunking included:** split by page or by token count, with token counts on every chunk.
- **Fair billing:** pay per page. Failed documents are never charged, and runs stop cleanly at
  your spending limit.

### Modes

| | `fast` (default) | `accurate` |
|---|---|---|
| Best for | Most RAG and LLM ingestion | Dense forms, multi-column layouts, tables you need exact |
| How it works | Reads the PDF's own text layer and extracts tables with a line-based detector; OCR only on pages with no text | Full layout model plus a table-structure recognition model |
| Speed on Apify (text page) | ~0.5 s | ~15–20 s (tables: up to ~2 min per dense page) |
| Price | $0.003 per page | $0.012 per page + $0.05 per table |
| Memory | Default is fine | **Needs 4096 MB.** If the run has less, it stops before charging anything |

DOCX, PPTX, XLSX and HTML are converted with native parsers in both modes. They're fast, and
billed as normal pages.

### Pricing

Pay per event. Platform usage is included, so you only pay the prices below.

| Event | Price |
|---|---|
| Page (fast mode PDF page, or any DOCX/PPTX/XLSX/HTML page) | **$3.00 per 1,000** |
| OCR page (a scanned page that needed OCR, either mode) | $10.00 per 1,000 |
| Accurate-mode page | $12.00 per 1,000 |
| Accurate-mode table | $50.00 per 1,000 |
| Actor start | $0.001 per GB of run memory (e.g. $0.002 at 2 GB) |

**Examples:**

| Job | Cost |
|---|---|
| A 100-page text PDF, fast mode | $0.30 plus start |
| 10 scanned pages | $0.10 plus start |
| A 20-page PDF with 5 tables, accurate mode | $0.24 + $0.25 = $0.49 plus start |
| A document that fails to download or convert | $0.00 |

Set a **maximum cost per run** in the run options, and docmd stops cleanly before going over it.

### Input

| Field | Default | Notes |
|---|---|---|
| `sources` | (required) | URLs of the documents (PDF, DOCX, PPTX, XLSX, HTML). The type is detected from the file contents, so URLs without an extension work too. |
| `mode` | `fast` | `fast` or `accurate`. See above. |
| `outputFormat` | `both` | `markdown`, `json` or `both`. |
| `chunking` | `none` | `none`, `page` or `tokens`. |
| `chunkTokens` | `800` | Target chunk size when `chunking` is `tokens` (50–8000). |
| `ocr` | `auto` | `auto` (only pages with no text layer), `off` or `force`. |
| `tableFormat` | `markdown` | How each entry in `tables` is encoded: `markdown`, `html` or `json`. The Markdown body always uses Markdown tables. |
| `maxPagesPerDocument` | `50` | Spend guard: pages beyond this are skipped and not charged (1–2000). |

```json
{
  "sources": [
    { "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf" },
    { "url": "https://example.com/report.docx" }
  ],
  "mode": "fast",
  "chunking": "tokens",
  "chunkTokens": 800
}
```

### Output

One dataset item per document:

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf",
  "fileName": "f1040.pdf",
  "fileType": "pdf",
  "mode": "fast",
  "pageCount": 2,
  "markdown": "# Form 1040 ...",
  "tables": [
    { "index": 0, "page": 1, "rows": 3, "columns": 3, "content": "| ... |" }
  ],
  "chunks": [
    { "text": "...", "page": 1, "tokens": 412 }
  ],
  "totalTokens": 1748,
  "ocrPages": 0,
  "status": "ok",
  "error": null
}
```

If a document can't be processed (unreachable URL, non-2xx response, a file over 100 MB, a
corrupt file or an unsupported format), its item has `"status": "failed"` and a short `error`,
and **it isn't charged**.

**How pages are counted:**

- PDF: pages.
- PPTX: slides.
- XLSX: sheets.
- DOCX and HTML: these have no real pages, so a page is counted per ~3,000 characters of output.

### Limitations

- **This is machine extraction.** Check important numbers, especially from tables and scans.
- **Fast-mode tables** are found from ruling lines. Tables drawn only with shading can be missed,
  and form boxes are sometimes detected as small tables. Use `accurate` mode if tables matter.
- **Fast-mode text** keeps the PDF's own line order. It doesn't detect columns or headings, and
  paragraphs may run together. Accurate mode rebuilds reading order and structure.
- **OCR text** is plain lines in reading order, with no layout.
- **Scanned-page detection:** in `auto` mode, a page counts as scanned when it has almost no text
  layer. A full-page image with a short real caption may not get OCR, so use `ocr: force` for those.
- **`maxPagesPerDocument`** is applied while parsing PDFs. DOCX, PPTX, XLSX and HTML files are
  parsed in full.

### Data handling

- Each document is downloaded to a temporary file (up to 100 MB), converted, then deleted, even
  when an error occurs.
- Output goes only to your own dataset for the run.
- Logs contain URLs, counts and timings only, never document content.

### Licences

Built only on permissively licensed components: docling (MIT), docling models
(CDLA-Permissive-2.0 / Apache-2.0), RapidOCR (Apache-2.0), pypdfium2 (Apache-2.0 / BSD-3-Clause),
pdfplumber and pdfminer.six (MIT), tiktoken (MIT), Apify SDK (Apache-2.0) and requests
(Apache-2.0). There are **no GPL or AGPL dependencies**, so the output is safe to use in
commercial products.

### Support

Found a problem or need a format? Open an issue on this Actor's **Issues** tab. We reply within
3 business days.

# Changelog

This Actor's version history is a separate document: https://apify.com/maxatron/docmd/changelog.md

# Actor input Schema

## `sources` (type: `array`):

URLs of documents to convert. Supported: PDF (text or scanned), DOCX, PPTX, XLSX, HTML.

## `mode` (type: `string`):

fast (default): pypdfium2 text extraction + pdfplumber tables + RapidOCR for scanned pages only. About as fast as reading the file (roughly 0.3-1s/page on a normal text PDF), priced near the market rate, and good enough for most RAG/LLM use. Docling's ML models never load. accurate: full docling layout analysis + TableFormer table-structure recognition. Much slower (roughly 15-70s/page on a 4 GB/1 vCPU worker) and priced higher (accurate-page + table charges), but reconstructs reading order, headings and complex table structure more faithfully - worth it for dense forms, multi-column layouts, or tables you need pixel-accurate. DOCX/PPTX/XLSX/HTML always use docling's fast native (non-ML) backends in both modes. Memory: fast mode runs fine at the default 2048 MB. Accurate mode needs 4096 MB; set it in the run options, or the run stops before charging anything.

## `outputFormat` (type: `string`):

Which fields to fill on each dataset item.

## `chunking` (type: `string`):

How to split each document's text into the `chunks` output field.

## `chunkTokens` (type: `integer`):

Target chunk size in tokens, used only when chunking = "tokens".

## `ocr` (type: `string`):

auto: OCR only pages with little or no extractable text (scanned pages). off: never OCR. force: OCR every PDF page.

## `tableFormat` (type: `string`):

Encoding used for each entry in the structured `tables` output field. The Markdown body always renders tables as Markdown for readability.

## `maxPagesPerDocument` (type: `integer`):

Spend guard: stop converting a single document after this many pages. Extra pages are skipped, not charged.

## Actor input object example

```json
{
  "sources": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf"
    }
  ],
  "mode": "fast",
  "outputFormat": "both",
  "chunking": "none",
  "chunkTokens": 800,
  "ocr": "auto",
  "tableFormat": "markdown",
  "maxPagesPerDocument": 50
}
```

# Actor output Schema

## `documents` (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 = {
    "sources": [
        {
            "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maxatron/docmd").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 = { "sources": [{ "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf" }] }

# Run the Actor and wait for it to finish
run = client.actor("maxatron/docmd").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 '{
  "sources": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf"
    }
  ]
}' |
apify call maxatron/docmd --silent --output-dataset

```

## MCP server setup

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

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/efoJG2RYmMgtkWSmM/builds/vZWZjOrAhGnHcyjRU/openapi.json
