# PDF to Markdown — Text, Tables & RAG Chunks for LLMs (`scarlet_harrier/pdf-to-markdown`) Actor

Convert PDFs from URLs or uploads into clean Markdown and plain text with headings, lists, paragraphs and basic tables. Page ranges, optional token-sized chunks with overlap, metadata. Pure text extraction, no OCR, no browser.

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

## Pricing

from $2.00 / 1,000 pdf 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 to Markdown — Text, Tables & RAG Chunks for LLMs

Give it PDF links. Get back clean Markdown and plain text — with headings, paragraphs, lists and basic tables rebuilt from the page layout — plus title, author and creation date. Optionally split into token-sized chunks with overlap, ready for embeddings and retrieval.

### What it does

- **Rebuilds structure**, not just text: headings (from font size, weight and numbering such as `3.2.1`), paragraphs, bullet and numbered lists, reference lists, and simple tables
- **Cleans the noise**: removes repeating page headers, footers and page numbers, joins words hyphenated across line breaks, fixes ligatures, merges paragraphs that continue across pages
- **Reads multi-column layouts** such as academic papers in the right order
- **Page ranges**: convert only `1-5, 8, 10-` if that is all you need
- **Chunking for RAG**: split on paragraph and heading boundaries into chunks of about N tokens with overlap, each with its page range and index. A heading is never stranded at the end of a chunk
- **Metadata**: title, author, creation date, page count
- **Honest about scans**: image-only PDFs are reported as `no_extractable_text` instead of returning an empty or invented result. There is no OCR
- **No browser, no proxies** — pure text extraction, so it's fast and cheap

### Use cases

- Building a RAG knowledge base from reports, papers, manuals and policy documents
- Feeding long documents to an LLM as clean Markdown
- Archiving and searching a library of PDFs
- Extracting a section of a long report by page range

### Input

| Field | Description |
|---|---|
| `pdfUrls` | Direct links to PDF files. |
| `keyValueStoreKeys` | Optional keys of PDF files stored in the run's default key-value store. |
| `pageRange` | Optional, e.g. `1-5, 8, 10-`. Empty converts every page. |
| `outputMode` | `markdown` (default), `text` or `both`. |
| `enableChunking` | Return one row per chunk instead of one per document. Default off. |
| `chunkSizeTokens` | Target maximum chunk size. Default 500. Tokens are estimated at about 4 characters each. |
| `chunkOverlapTokens` | Overlap between consecutive chunks. Default 50. |

```json
{
  "pdfUrls": ["https://arxiv.org/pdf/1706.03762"],
  "pageRange": "1-5",
  "outputMode": "both",
  "enableChunking": true,
  "chunkSizeTokens": 500,
  "chunkOverlapTokens": 50
}
```

### Output

One row per document, or per chunk when chunking is on.

```json
{
  "sourceUrl": "https://arxiv.org/pdf/1706.03762",
  "status": "ok",
  "error": null,
  "title": "Attention Is All You Need",
  "author": null,
  "createdDate": "2024-04-10T21:11:43.000Z",
  "pageCount": 15,
  "pagesConverted": 15,
  "pagesWithoutText": [],
  "pageStart": 3,
  "pageEnd": 4,
  "chunkIndex": 5,
  "chunkCount": 24,
  "tokenEstimate": 490,
  "markdown": "follows this overall architecture using stacked self-attention and point-wise, fully connected layers…",
  "text": "follows this overall architecture using stacked self-attention and point-wise, fully connected layers…"
}
```

`status` is `ok`, `no_extractable_text` or `error` (with the reason in `error`). Documents that are not a PDF, are password-protected, or fail to download get an `error` row and are not charged.

### Pricing

Pay per page converted. Only pages that contain extractable text are charged; blank, image-only and failed pages are free.

### Notes

- Works on PDFs that contain real text. Scanned documents need OCR, which this Actor does not do.
- Layout reconstruction is heuristic. Typical reports, papers and forms convert well. Complex multi-column designs, equations and tables without ruling or clear alignment are best-effort: expect equations to come out as plain text fragments and some tables as paragraphs.
- The title comes from the PDF's metadata when it is meaningful, otherwise from the largest heading on page 1 (only when page 1 is included).
- Limits: 100 MB per file and 10 minutes per document.
- This Actor was built and is maintained with AI assistance, with human review. Issues are answered within a few days.

# Actor input Schema

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

Direct links to PDF files.

## `keyValueStoreKeys` (type: `array`):

Keys of PDF files you have stored in this run's default key-value store.

## `pageRange` (type: `string`):

Optional. Pages to convert, e.g. 1-5, 8, 10- (open-ended ranges allowed). Empty converts every page.

## `outputMode` (type: `string`):

Which fields to return.

## `enableChunking` (type: `boolean`):

Return one row per chunk instead of one row per document, split on paragraph and heading boundaries.

## `chunkSizeTokens` (type: `integer`):

Target maximum chunk size. Tokens are estimated at about 4 characters each.

## `chunkOverlapTokens` (type: `integer`):

How much of the end of one chunk is repeated at the start of the next.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "outputMode": "markdown",
  "enableChunking": false,
  "chunkSizeTokens": 500,
  "chunkOverlapTokens": 50
}
```

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

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

# Run the Actor and wait for it to finish
run = client.actor("scarlet_harrier/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": [
    "https://arxiv.org/pdf/1706.03762"
  ]
}' |
apify call scarlet_harrier/pdf-to-markdown --silent --output-dataset

```

## MCP server setup

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