# PDF to Markdown & Text for LLM/RAG (OCR, Tables, Chunks) (`integriapps/pdf-to-markdown`) Actor

Convert PDFs (URLs or uploaded files) to clean Markdown and text with GitHub tables, per-page metadata and token-sized chunks for RAG and AI agents. Optional Tesseract OCR for scanned pages (English, Spanish and more) and a Mexican CFDI invoice mode. Pay per page. By IntegriApps.

- **URL**: https://apify.com/integriapps/pdf-to-markdown.md
- **Developed by:** [IntegriApps .com](https://apify.com/integriapps) (community)
- **Categories:** AI, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 pdf page extracteds

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 to Markdown & Text for LLM/RAG (OCR, Tables, Chunks)

Convert **PDF files to clean Markdown and plain text** ready for **LLMs, RAG pipelines, embeddings, vector databases and AI agents**. Give it PDF links or upload your files: every document comes back with **Markdown tables**, headings, **per-page metadata**, document metadata and **token-sized chunks with page numbers**. Scanned or image-only pages can be read with **Tesseract OCR** (English, Spanish, Portuguese, French, German, Italian, Hindi). A special **CFDI mode** pulls the key fields out of Mexican invoice PDFs.

Part of [IntegriApps MicroApps](https://integriapps.com/microapps/). Support Sunday to Friday.

### What it does

- **PDF → Markdown:** headings (detected from font size), paragraphs (wrapped lines are joined, hyphenated words repaired), bullet and numbered lists, and **ruled tables as GitHub-flavored Markdown tables**.
- **Multi-column layouts:** two- and three-column pages (papers, IRS publications, brochures) are read column by column instead of mixing lines across columns.
- **Per-page metadata:** page number, size, extraction method (`text`, `ocr` or `empty`), OCR confidence, tables, images, characters and words per page.
- **Document metadata:** title, author, subject, keywords, creator, producer, creation and modification dates, page count, file size and SHA-1.
- **RAG-ready chunks:** the document is split into chunks of ~N tokens (1 token ≈ 4 characters) on block boundaries, with overlap, **start/end page** and the nearest heading.
- **OCR for scanned PDFs:** `auto` mode runs Tesseract only on pages without a text layer, so normal PDFs stay cheap and fast; `always` mode OCRs every page.
- **CFDI mode (Mexico):** detects UUID (folio fiscal), RFC emisor and receptor, subtotal, total, issue and certification dates, currency, CFDI type, series/folio, SAT certificate number, uso CFDI, método and forma de pago in printed CFDI 4.0 invoices.
- **Three input options:** PDF URLs, files uploaded in the input form, or every PDF in one of your key-value stores (bulk).
- **Safe by design:** downloads only the files you give it (no crawling), retries network errors with backoff, skips files over your size limit, and never charges for failed or non-PDF files.

### Use cases

- Load manuals, contracts, reports, papers or policies into a **RAG chatbot** or **vector database** (Pinecone, Qdrant, pgvector, Weaviate, Chroma…).
- Give an **AI agent / MCP client** a tool that reads a PDF and returns Markdown.
- Make **scanned documents searchable** with OCR.
- Pull **invoice data from Mexican CFDI PDFs** when you don't have the XML (accounting, expense control, reconciliation).
- Bulk-convert a folder of PDFs exported by another Actor or uploaded via the API.

### Input

| Field | Description | Default |
|---|---|---|
| `pdfUrls` | Direct links to PDF files | — |
| `files` | Upload PDFs (stored in an Apify key-value store) or paste file URLs | — |
| `keyValueStoreId` | Pick a key-value store: all its PDF records are processed (read-only access to that store only) | — |
| `keyPrefix` | Only process records whose key starts with this prefix | — |
| `ocrMode` | `auto` (OCR only pages without text), `off`, `always` | `auto` |
| `ocrLanguages` | Tesseract languages: `eng`, `spa`, `por`, `fra`, `deu`, `ita`, `hin` | `["eng","spa"]` |
| `ocrDpi` | OCR rendering resolution (100–400) | 300 |
| `cfdiMode` | Detect Mexican CFDI invoice fields | false |
| `extractTables` | Ruled tables as Markdown tables | true |
| `chunkSize` / `chunkOverlap` | Chunk size and overlap in tokens (0 = no chunks) | 800 / 100 |
| `pageMarkers` | `<!-- page N -->` comments between pages | true |
| `includePlainText` | Also return `text` without page markers | false |
| `maxPagesPerPdf` | Only the first N pages are processed and charged (0 = all, up to 5,000) | 500 |
| `maxFileSizeMb` | Larger files are skipped, not charged (1–200) | 50 |
| `password` | Password for encrypted PDFs (stored encrypted) | — |
| `maxConcurrency` | PDFs processed in parallel (1–4) | 2 |

Empty input converts a small bundled sample PDF **for free**, so you can see the output format.

Example — two PDFs, OCR only where needed, smaller chunks:

```json
{
  "pdfUrls": [
    { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
    { "url": "https://arxiv.org/pdf/1706.03762" }
  ],
  "ocrMode": "auto",
  "chunkSize": 500,
  "chunkOverlap": 50
}
```

### Output

One dataset item per PDF. Real output of the free demo run (shortened):

```json
{
  "source": "sample.pdf",
  "status": "ok",
  "fileSizeBytes": 3176,
  "metadata": { "title": "Quarterly Report", "author": "IntegriApps Test", "producer": "ReportLab PDF Library - (opensource)", "createdAt": "2026-11-02T11:10:14-06:00" },
  "pageCount": 2,
  "pagesProcessed": 2,
  "truncated": false,
  "ocrPages": 0,
  "tableCount": 1,
  "wordCount": 305,
  "tokenEstimate": 463,
  "markdown": "<!-- page 1 -->\n\n# Quarterly Report 2026\n\n## 1. Summary\n\nRetrieval augmented generation works best when ...\n\n- Revenue grew 12%\n- Costs fell 3%\n- New customers: 41\n\n## 2. Sales by region\n\n| Region | Q1 | Q2 | Total |\n|---|---|---|---|\n| North | 1,200 | 1,350 | 2,550 |\n| South | 980 | 1,010 | 1,990 |\n| Online | 2,400 | 2,900 | 5,300 |\n\nNumbers are in thousands of USD.\n\n<!-- page 2 -->\n\n## 3. Outlook\n\n...",
  "pages": [
    { "pageNumber": 1, "width": 612, "height": 792, "extraction": "text", "layoutSegments": 1, "tableCount": 1, "imageCount": 0, "charCount": 901, "wordCount": 161 },
    { "pageNumber": 2, "width": 612, "height": 792, "extraction": "text", "layoutSegments": 1, "tableCount": 0, "imageCount": 0, "charCount": 914, "wordCount": 144 }
  ],
  "chunks": [
    { "index": 0, "text": "# Quarterly Report 2026\n\n## 1. Summary\n\n...", "tokens": 455, "pageStart": 1, "pageEnd": 2, "heading": "Quarterly Report 2026" }
  ]
}
```

Real `cfdi` object from a sample invoice with SAT test data (`cfdiMode: true`):

```json
"cfdi": {
  "isCfdi": true,
  "uuid": "6F8A1C2E-3B4D-4E5F-9A0B-1C2D3E4F5A6B",
  "rfcEmisor": "EKU9003173C9",
  "rfcReceptor": "XAXX010101000",
  "subtotal": 1000,
  "total": 1160,
  "fechaEmision": "2026-10-15T10:22:31",
  "fechaTimbrado": "2026-10-15T10:23:02",
  "moneda": "MXN",
  "tipoComprobante": "I",
  "serie": "A",
  "folio": "1024",
  "noCertificadoSat": "30001000000500003456",
  "usoCfdi": "S01",
  "metodoPago": "PUE",
  "formaPago": "03",
  "fieldsFound": 15
}
```

Fields that are not found are `null`.

`status` is one of `ok`, `not_a_pdf`, `invalid_pdf`, `password_protected`, `download_error`, `file_too_large`, `error`, `charge_limit_reached` or `skipped_charge_limit`. Only processed pages of `ok` documents are charged. The key-value store also gets a `RUN_SUMMARY` record (documents, pages, OCR pages, charged pages). Export the dataset as JSON, JSONL, CSV or Excel, or use the **CFDI** dataset view for a flat invoice table.

### Pricing

**Pay per event, per page:**
| Event | Price | When |
|---|---|---|
| `page-extracted` | **USD 0.001** per page (USD 1 per 1,000 pages) | A page converted from its text layer |
| `ocr-page` | **USD 0.004** per page (USD 4 per 1,000 pages) | A page read with OCR (instead of `page-extracted`) |

Chunks, metadata, tables and CFDI mode are included. **Not charged:** blank pages, pages where OCR finds no text, failed downloads, non-PDF files, encrypted PDFs without the right password and files over your size limit. Apify also adds its small standard Actor-start fee. Examples: a 10-page text PDF costs USD 0.01; a 10-page scan costs USD 0.04. Use `maxPagesPerPdf` and the run's maximum charge setting to cap spending — the Actor stops cleanly at the limit.

### Use it from your code

```bash
curl -X POST "https://api.apify.com/v2/acts/integriapps~pdf-to-markdown/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pdfUrls":[{"url":"https://www.irs.gov/pub/irs-pdf/fw9.pdf"}],"chunkSize":0}'
```

It also works from the Apify API clients (JavaScript/Python), Make, Zapier, n8n, LangChain/LlamaIndex Apify integrations and the Apify MCP server.

### FAQ

**Which tables are converted to Markdown tables?** Tables drawn with lines (ruled cells), as in invoices, forms and most business reports. Borderless tables (for example many academic papers) come out as text lines in reading order.

**How good is the OCR?** It uses Tesseract 5, which works well on clean scans at 300 DPI (the per-page `ocrConfidence` tells you how sure it was). Handwriting, very low-resolution or skewed scans give poor results. OCR output is plain paragraphs (no table detection on scanned pages).

**Does it handle multi-column PDFs?** Yes, columns of prose are read one after the other. Very complex layouts (magazines, mixed boxes and sidebars) may still come out partly out of order.

**Is the CFDI mode a validation against the SAT?** No. It reads the printed invoice text with heuristics and returns what it finds; it does not verify the invoice status with the SAT or check the digital seal. When the data matters, use the CFDI XML (for example with our [CFDI 4.0 Parser & Validator](https://apify.com/integriapps/cfdi-parser) Actor).

**Can it open password-protected PDFs?** Yes, if you give the password in `password`. Otherwise the item gets `password_protected` and is not charged.

**Can it read PDFs behind a login?** No. Give it public links, signed links, uploaded files or a key-value store you select. The Actor runs with limited permissions and only reads the store you pick.

**Is my data stored somewhere else?** No. Files are processed in memory inside your run; results are saved only in your run's dataset and key-value store.

**How are tokens counted?** As an estimate of 4 characters per token, close to common LLM tokenizers for English text.

### Changelog

See [CHANGELOG.md](./CHANGELOG.md).

Part of [IntegriApps MicroApps](https://integriapps.com/microapps/) — small tools by IntegriApps, a SaaS for SMBs. Support Sunday to Friday.

# Changelog

This Actor's version history is a separate document: https://apify.com/integriapps/pdf-to-markdown/changelog.md

# Actor input Schema

## `pdfUrls` (type: `array`):

Direct links to PDF files (http/https). Only these files are downloaded; nothing is crawled. If all inputs are empty, a bundled sample PDF is converted for free as a demo.

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

Upload PDFs from your computer (they are stored in an Apify key-value store) or paste file URLs.

## `keyValueStoreId` (type: `string`):

Optional. Pick one of your key-value stores: every PDF record in it is processed (e.g. files uploaded by another Actor or via the API). Read-only access is requested only for this store.

## `keyPrefix` (type: `string`):

Optional. With a key-value store, only records whose key starts with this prefix are processed.

## `ocrMode` (type: `string`):

'auto' runs Tesseract OCR only on pages without a text layer (scanned/image pages). 'always' OCRs every page (ignores the text layer). 'off' never OCRs. OCR pages are charged as 'ocr-page' instead of 'page-extracted'.

## `ocrLanguages` (type: `array`):

Languages for Tesseract OCR. Fewer languages = faster and more accurate.

## `ocrDpi` (type: `integer`):

Rendering resolution for OCR (100–400). 300 is a good balance; 200 is faster for large clean scans.

## `cfdiMode` (type: `boolean`):

Detect key fields of Mexican CFDI invoice PDFs (UUID/folio fiscal, RFC emisor and receptor, subtotal, total, dates, currency, series/folio, payment method). Heuristic: verify against the XML when it matters. No extra charge.

## `extractTables` (type: `boolean`):

Detect ruled tables and output them as GitHub-flavored Markdown tables.

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

Split each PDF into chunks of about this many tokens (1 token ≈ 4 characters) for RAG/embeddings, with page numbers. 0 = no chunks.

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

Tokens repeated between consecutive chunks to keep context.

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

Insert  comments between pages in the Markdown.

## `includePlainText` (type: `boolean`):

Also return a 'text' field without page markers.

## `maxPagesPerPdf` (type: `integer`):

Only the first N pages of each PDF are processed (and charged). 0 = all pages (up to 5,000).

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

Larger files are skipped and not charged (1–200).

## `password` (type: `string`):

Optional password for encrypted PDFs (applies to all files). Stored encrypted.

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

PDFs processed in parallel (1–4).

## Actor input object example

```json
{
  "pdfUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng",
    "spa"
  ],
  "ocrDpi": 300,
  "cfdiMode": false,
  "extractTables": true,
  "chunkSize": 800,
  "chunkOverlap": 100,
  "pageMarkers": true,
  "includePlainText": false,
  "maxPagesPerPdf": 500,
  "maxFileSizeMb": 50,
  "maxConcurrency": 2
}
```

# Actor output Schema

## `documents` (type: `string`):

No description

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

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

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

```

## MCP server setup

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