# PDF to Markdown (RAG-ready): Scans + Tables (`copy2paste/pdf-to-markdown-rag`) Actor

Scanned PDFs and messy tables actually convert here. Bundled OCR reads image-only pages; tables come out as real Markdown tables. Benchmarked against the leading alternatives — results in the README.

- **URL**: https://apify.com/copy2paste/pdf-to-markdown-rag.md
- **Developed by:** [Dermot O'Brien](https://apify.com/copy2paste) (community)
- **Categories:** AI
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 text page converteds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## PDF to Markdown (RAG-ready)

**Turn any PDF — text-native or scanned — into clean Markdown for RAG pipelines, LLM context, and AI agents.**

Most PDF converters stop at plain text and lose table structure on the way, and plenty return nothing at all for scanned pages. This one runs OCR on scanned PDFs, recovers tables as real GFM Markdown tables, and outputs a manifest and page records per document, so one bad document in a batch doesn't stop the rest.

### Why this one

- **Scans actually get OCR'd.** Image-only PDFs run through bundled Tesseract OCR instead of coming back empty.
- **Tables stay tables.** GFM table recovery outputs real Markdown tables, not flattened text — see the benchmark below.
- **One bad file doesn't sink the run.** Only pages that convert successfully turn into records, and every document gets its own manifest, so a bad document doesn't stop the rest of the batch.

### Quickstart

Convert a PDF by URL with the defaults (auto-OCR, Markdown only, 200-page cap):

```json
{ "documents": [{ "url": "https://arxiv.org/pdf/2506.22653" }] }
````

Multiple documents with chunked output, ready for a vector DB:

```json
{
  "documents": [
    { "url": "https://example.com/a.pdf" },
    { "key": "UPLOADED_FILE_KEY" }
  ],
  "output": "markdown+chunks",
  "chunking": { "maxTokens": 1024, "overlapTokens": 64 }
}
```

### What you get

Each run produces one dataset record per converted page (`kind: "page"`), one manifest per document (`kind: "document"`) pointing at the full Markdown in the run's key-value store (`fullMarkdownKey`), and an `OUTPUT` record with the run summary. Add `"output": "markdown+chunks"` to also get fixed-window token chunks sized for a vector DB. Trigger it the same way from the Console, the API, Make/n8n, or an MCP-connected agent.

### Measured, not claimed

Tested against the three highest-usage runnable PDF-extraction actors on a frozen 20-document public corpus — government reports, CC-BY papers, true image-only scans, table-heavy statistical documents, and pathological files — with table ground truth labeled before any contender ran. Full protocol, corpus manifest, raw outputs, and scoring code are available on request via the Issues tab.

| Contender | Conversion success | Table-cell F1 | Scanned-page OCR |
|---|---|---|---|
| **This actor** | **17/17** | **0.911** | **exact text (0.0 CER)** |
| Best incumbent A | 15/17 | 0.903 | no output |
| Best incumbent B | 15/17 | 0.000 | no output |
| Best incumbent C | 7/17 (timeouts) | 0.000 | exact on completed |

Honest caveat: one incumbent edges ahead on a single dense-table document class (0.941 vs 0.889 on that page), and none of the others can read a scanned page and hold table structure at the same time this actor does.

### Limits and good citizenship

100 documents per run, 100 MB per input, up to 500 pages per document (200 by default), and OCR in English for this version. DOCX, PPTX, XLSX, and HTML input are on the roadmap, not available yet. The actor also respects whatever spending limit you set for the run and stops picking up new work the moment that limit is hit.

### Privacy and security

Your documents live in Actor memory and an isolated temporary directory for the length of the run; that directory is deleted the moment a document finishes processing, and the actor creator keeps no separate copy. Document content never appears in logs, and raw URL queries, fragments, and credentials are never persisted or logged. What does persist is the derived output — page records, Markdown, chunks, manifests, and the run summary — in storage under your own Apify account, governed by your plan, your retention settings, and Apify's DPA. You're responsible for having the right to process whatever documents you submit; this tool isn't offered for regulated data such as HIPAA-class records.

Converted output is untrusted content, not instructions. If you hand it to a downstream agent, treat it as data to read, not as something to execute, and use the source reference, document index, and page number carried in each record for provenance.

This is an independent project, not affiliated with or endorsed by IBM, Docling, or Apify. It's AI-built and AI-operated under the account owner's supervision, with every release passing an automated test suite and cross-model code review before it ships.

# Actor input Schema

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

Array of 1–100 items, each exactly one of {"url": "https://..."} or {"key": "NAME\_IN\_RUN\_KV\_STORE"}. URLs must be http(s) without credentials.

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

auto: OCR pages with no extractable text (billed at the OCR page rate). off: skip such pages with a warning.

## `output` (type: `string`):

Store Markdown only, or Markdown plus deterministic token chunks for RAG.

## `chunking` (type: `object`):

Only valid with output = markdown+chunks. {"maxTokens": 128–4096 (default 1024), "overlapTokens": 0–512 (default 64), overlap < maxTokens/2}.

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

Convert at most this many pages from each PDF (1–500).

## Actor input object example

```json
{
  "documents": [
    {
      "url": "https://arxiv.org/pdf/2506.22653"
    }
  ],
  "ocr": "auto",
  "output": "markdown",
  "maxPagesPerDocument": 200
}
```

# 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 = {
    "documents": [
        {
            "url": "https://arxiv.org/pdf/2506.22653"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("copy2paste/pdf-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 = { "documents": [{ "url": "https://arxiv.org/pdf/2506.22653" }] }

# Run the Actor and wait for it to finish
run = client.actor("copy2paste/pdf-to-markdown-rag").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "documents": [
    {
      "url": "https://arxiv.org/pdf/2506.22653"
    }
  ]
}' |
apify call copy2paste/pdf-to-markdown-rag --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=copy2paste/pdf-to-markdown-rag",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "PDF to Markdown (RAG-ready): Scans + Tables",
        "description": "Scanned PDFs and messy tables actually convert here. Bundled OCR reads image-only pages; tables come out as real Markdown tables. Benchmarked against the leading alternatives — results in the README.",
        "version": "0.1",
        "x-build-id": "7XA5dLKcoHBY3vklt"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/copy2paste~pdf-to-markdown-rag/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-copy2paste-pdf-to-markdown-rag",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/copy2paste~pdf-to-markdown-rag/runs": {
            "post": {
                "operationId": "runs-sync-copy2paste-pdf-to-markdown-rag",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/copy2paste~pdf-to-markdown-rag/run-sync": {
            "post": {
                "operationId": "run-sync-copy2paste-pdf-to-markdown-rag",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "documents"
                ],
                "properties": {
                    "documents": {
                        "title": "PDF documents",
                        "type": "array",
                        "description": "Array of 1–100 items, each exactly one of {\"url\": \"https://...\"} or {\"key\": \"NAME_IN_RUN_KV_STORE\"}. URLs must be http(s) without credentials."
                    },
                    "ocr": {
                        "title": "OCR mode",
                        "enum": [
                            "auto",
                            "off"
                        ],
                        "type": "string",
                        "description": "auto: OCR pages with no extractable text (billed at the OCR page rate). off: skip such pages with a warning.",
                        "default": "auto"
                    },
                    "output": {
                        "title": "Output mode",
                        "enum": [
                            "markdown",
                            "markdown+chunks"
                        ],
                        "type": "string",
                        "description": "Store Markdown only, or Markdown plus deterministic token chunks for RAG.",
                        "default": "markdown"
                    },
                    "chunking": {
                        "title": "Chunking options",
                        "type": "object",
                        "description": "Only valid with output = markdown+chunks. {\"maxTokens\": 128–4096 (default 1024), \"overlapTokens\": 0–512 (default 64), overlap < maxTokens/2}."
                    },
                    "maxPagesPerDocument": {
                        "title": "Maximum pages per document",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Convert at most this many pages from each PDF (1–500).",
                        "default": 200
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
