# PDF & Office to Markdown for RAG — tables, chunks, OCR (`lif_manager/documents-to-markdown-rag`) Actor

Convert PDFs, scans, images, Word, Excel and PowerPoint into clean Markdown for LLMs and RAG: real tables (Markdown, CSV, cells), correct reading order in multi-column layouts, headers and footers removed, and ready-to-embed chunks with page and section. Automatic OCR only on scanned pages.

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

## Pricing

from $1.00 / 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 & Office to Markdown for RAG — tables, chunks, OCR

Turn PDFs, scanned documents, images, Word, Excel and PowerPoint files into **clean Markdown that LLMs and RAG pipelines can use directly**:

- 📊 **Tables come out as tables** — as Markdown, CSV and a cell array (`rows`), not as jumbled lines of text. Tables whose ruling only separates groups of rows are split back into one row per record.
- 📰 **Correct reading order** in two- and three-column layouts (papers, reports, government gazettes).
- 🧹 **Repeated headers, footers and page numbers removed**, and paragraphs broken across columns or pages joined back together.
- 🧩 **Ready-to-embed chunks** that follow section boundaries, carry their **page range and section path** (`Introduction > Method`), and never cut a table without repeating its header row.
- 🔍 **Automatic OCR only where needed**: pages without a usable text layer and images are OCR'd (Spanish + English by default; Portuguese, French, German and Italian available). Pages with text are not, so they stay fast and cheap.
- 📁 **One Actor for every format**: PDF, PNG, JPG, WEBP, TIFF, BMP, DOCX, XLSX, PPTX (including speaker notes), TXT, MD and CSV.

No LLM inside: deterministic, fast (≈0.2 s per text page) and your documents are not sent to any third-party AI service.

### How to use it

1. Paste public URLs of your documents, **or** upload files and select the storage they were uploaded to in **Storage with uploaded files** (the Actor runs with limited permissions and can only read the storage you choose).
2. Pick the output: one item per **document**, per **chunk** (ready for a vector database) or per **page**.
3. Run it. Leave the input empty to try a demo document (not charged).

From code or an AI agent, call it through the Apify API or MCP with `{"documents": ["https://example.com/report.pdf"]}`.

### Input

| Field | Default | What it does |
|---|---|---|
| `documents` | — | Files or URLs (up to 50 MB each). |
| `storage` | — | The key-value store with your uploaded files. |
| `output_mode` | `document` | `document`, `chunks` or `pages`. |
| `chunk_tokens` | 800 | Target chunk size (≈4 characters per token). 0 = no chunks. |
| `chunk_overlap` | 80 | Text repeated at the start of the next chunk when a section is split by size. |
| `ocr` | `auto` | `auto`, `always` (PDFs with a broken text layer) or `never`. |
| `ocr_languages` | `spa+eng` | Languages of scanned pages. |
| `pages` | all | Page or slide range, e.g. `1-5, 8, 12-`. |
| `max_pages` | 200 | Safety limit per document. |
| `include_pages` | false | Adds per-page Markdown, method and OCR confidence. |
| `save_markdown_files` | true | Also saves a `.md` file per document, with a link in the result. |

### Output (document mode)

```json
{
  "file": "report.pdf",
  "status": "ok",
  "format": "pdf",
  "page_count": 12,
  "metadata": {"title": "Annual report", "author": "…", "creationdate": "2026-03-02T10:15:00+00:00"},
  "markdown": "# Annual report\n\n## Results\n\n| Plot | Water (m³) | Yield (kg) |\n| --- | --- | --- |\n| A1 | 1,200 | 3,450 |…",
  "tables": [{"table_id": 1, "page": 3, "n_rows": 4, "n_cols": 3, "rows": [["Plot", "Water (m³)", "Yield (kg)"], ["A1", "1,200", "3,450"]], "markdown": "…", "csv": "…"}],
  "chunks": [{"chunk_id": 1, "section": "Annual report > Results", "pages": [3, 4], "tokens_est": 612, "text": "…"}],
  "billing": {"page": 11, "ocr-page": 1},
  "stats": {"chars": 41250, "tokens_est": 10313, "n_tables": 4, "n_ocr_pages": 1, "removed_header_footer_lines": 22},
  "markdown_file": "https://api.apify.com/v2/key-value-stores/…/records/001-report.md",
  "warnings": []
}
```

In **chunks** mode each item is one chunk: `file`, `chunk_id`, `chunk_count`, `section`, `pages`, `tokens_est`, `text`, `title`. In **pages** mode each item is one page with its Markdown and tables.

### Pricing

Pay per event, only for pages that produced text:

| Event | Price | When |
|---|---|---|
| `page` | $0.001 | A PDF page with a text layer, a slide, or every 3,000 characters of DOCX/XLSX/TXT output |
| `ocr-page` | $0.003 | A scanned page or image that needed OCR and returned text |

Empty pages, failed downloads, unsupported files and the demo are **not charged**. The run stops when it reaches the maximum cost per run you set.

### Limits (honest list)

- Tables are detected on PDF pages with a text layer when they have ruling lines. Tables without lines and tables inside scanned pages come out as text (you get a warning).
- OCR is Tesseract: clean prints read very well; handwriting, very small text and photos with heavy blur do not.
- Headings in PDFs are inferred from font size and bold; Word headings come from their styles.
- Legacy `.doc`, `.xls` and `.ppt` are not supported (save them as DOCX/XLSX/PPTX or PDF). Password-protected PDFs are not supported.

### Privacy and permissions

- Network access only to download the files you give it. The Apify token is only ever sent to `api.apify.com`.
- Files are processed in the run's container and deleted when it ends. Results stay in your own dataset and key-value store.
- Limited permissions: the Actor can only read the storage you select.
- Open-source engines with permissive licenses (pdfplumber, pypdfium2, Tesseract, OpenCV, python-docx, openpyxl, python-pptx). Pinned dependencies.

### Feedback

Found a document that converts badly? Open an issue on the Actor page with a public link to it (or a similar one) and it will be looked at.

# Actor input Schema

## `documents` (type: `array`):

Upload files or paste public URLs. Formats: PDF (text or scanned), PNG, JPG, WEBP, TIFF, BMP, DOCX, XLSX, PPTX, TXT, MD, CSV. Up to 50 MB per file. If you upload files here, also select the storage they went to below. Leave empty to see a demo.

## `storage` (type: `string`):

The key-value store where your uploaded files are. This Actor runs with limited permissions and can only read the storage you pick here. If you leave the list above empty, every supported document in this storage is converted.

## `output_mode` (type: `string`):

document: one item with Markdown, tables and chunks. chunks: one item per chunk, ready for a vector database. pages: one item per page.

## `chunk_tokens` (type: `integer`):

Target chunk size, estimated as 4 characters per token. Chunks follow section boundaries and never split a table without repeating its header row. 0 = no chunks.

## `chunk_overlap` (type: `integer`):

Text repeated at the start of the next chunk when a section is split by size.

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

auto: OCR only pages without a usable text layer (and images). always: OCR every page (for PDFs with broken text). never: faster and cheaper, scanned pages come out empty.

## `ocr_languages` (type: `string`):

Languages in the scanned documents. The first one has priority.

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

Pages or slides to convert, e.g. 1-5, 8, 12-. Empty = all.

## `max_pages` (type: `integer`):

Safety limit per document (cost control).

## `include_pages` (type: `boolean`):

Adds a 'pages' array with the Markdown, method (text/ocr) and OCR confidence of each page.

## `save_markdown_files` (type: `boolean`):

Also saves each document's Markdown as a file in the run's key-value store, with a download link in the result.

## Actor input object example

```json
{
  "output_mode": "document",
  "chunk_tokens": 800,
  "chunk_overlap": 80,
  "ocr": "auto",
  "ocr_languages": "spa+eng",
  "max_pages": 200,
  "include_pages": false,
  "save_markdown_files": true
}
```

# Actor output Schema

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

No description

## `markdown_files` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("lif_manager/documents-to-markdown-rag").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("lif_manager/documents-to-markdown-rag").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 '{}' |
apify call lif_manager/documents-to-markdown-rag --silent --output-dataset

```

## MCP server setup

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

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/vXTbejBBR6qwEoalA/builds/VDBcnBIOFi6nyOO8x/openapi.json
