# PDF to Markdown Converter (`plainpipe/pdf-to-markdown`) Actor

Convert PDF links or files to clean Markdown: one row per page, per chunk or per file. Use when you need PDF text as Markdown for RAG, LLM prompts or search.

- **URL**: https://apify.com/plainpipe/pdf-to-markdown.md
- **Developed by:** [Plain Pipe](https://apify.com/plainpipe) (community)
- **Categories:** 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

$2.00 / 1,000 results

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 Converter

Convert PDF files or links to clean Markdown, with headings, lists and paragraphs rebuilt from the text layer. Get one row per page, one row per chunk of about N words, or one row per PDF.

**Use when:** you need the text of PDFs as Markdown for a RAG pipeline, an LLM prompt or a search index, with page numbers kept.

### Why this one

- **$2.00 per 1,000 results.** Each dataset row is one result, so you control the cost with the output mode.
- Pick pages (`"1-3,7"`), get one row per PDF (`outputMode: "document"`), or RAG-sized chunks (`chunkWords`) with `chunkIndex`, all in one Actor.
- Every row carries `page`, `pages`, `words` and `characters`, ready for citations and chunk planning.
- No browser and no proxy: plain HTTP fetch plus a text-layer parser, so the prefilled sample finishes in seconds. The prefilled input is tested daily.

### What it does

Give it PDF links or upload a PDF. It reads the text layer of each page, rebuilds lines and paragraphs from the text positions, turns larger-font lines into `#` / `##` headings and numbered section titles such as `2.1 Results` into `###` headings, joins words split by end-of-line hyphens, and turns bullet lines into Markdown lists. By default each page with text becomes one dataset row with its Markdown, page number, page count, word count and character count. You can limit the run to some pages, join all pages of a file into one row, or split long pages into chunks.

Pages without a text layer (scanned images) are skipped and not charged. Links that fail, return a web page instead of a PDF, or point at a damaged file are skipped with a warning, so one bad link does not stop a batch.

### Use cases

- Split reports, manuals and papers into Markdown chunks of about 300 words for a RAG pipeline or vector database.
- Get one Markdown row per PDF for a wiki page or a single LLM prompt.
- Convert only the pages you need, such as the summary on pages 1-2.
- Turn PDF documentation into Markdown for a wiki, static site or note app.
- Prepare PDF content for an LLM prompt without layout noise, with page numbers kept for citations.
- Count words and characters per page before translation or editing work.
- Run a nightly batch over a list of PDF links and feed the rows into another tool through the Apify API.

### How to use it

1. Paste direct PDF links into **PDF URLs**, one per line, or upload a file in **Upload a PDF**.
2. Optional: set **Pages** (for example `1-3,7`), pick **Output mode**, set **Chunk size (words)**, or lower **Max results**.
3. Click **Start**. Each page (or chunk, or PDF) appears as one row in the dataset.
4. Export the rows as JSON, CSV or Excel, or read them through the Apify API.

### Input

| Field | What it is | Default | Example |
|---|---|---|---|
| `pdfUrls` | Direct links to PDF files, one per line. The link must return the PDF itself, not a viewer page. | none | `["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]` |
| `pdfFile` | One uploaded PDF, instead of or in addition to links. | none | a file picked in the input form |
| `pages` | Pages to convert in every file: numbers and ranges, from 1. Empty = all pages. | all | `"1-3,7"` |
| `outputMode` | `"page"` = one row per page. `"document"` = one row per PDF, pages joined with `\n\n---\n\n`. | `"page"` | `"document"` |
| `chunkWords` | Page mode only. `0` = off. Otherwise split long pages into chunks of about this many words, cut between paragraphs, one row per chunk. Allowed: 0 or 20 to 100000. | `0` | `300` |
| `maxItems` | Stop after this many results (rows) in total, across all files. | `1000` | `20` |

Example input:

```json
{
  "pdfUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
  "maxItems": 5
}
```

### Output

In page mode (the default), one row per page that has text. Real output of the example input above:

```json
{
  "source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "page": 1,
  "pages": 1,
  "markdown": "Dummy PDF file",
  "characters": 14,
  "words": 3
}
```

| Field | Meaning |
|---|---|
| `source` | The link (or uploaded file) the page came from. |
| `page` | Page number in the file, starting at 1. |
| `pages` | Total number of pages in the file. |
| `markdown` | The page as Markdown: headings, paragraphs and lists. |
| `characters` / `words` | Size of the Markdown, for chunking and cost planning. |
| `chunkIndex` / `chunks` | Only when `chunkWords` is set: position of this chunk in its page (from 0) and the number of chunks for that page. |
| `pageCount` / `pageNumbers` | Only in document mode: how many pages with text were joined into this row, and which ones. |

In document mode a row looks like `{"source": "...", "pages": 15, "pageCount": 3, "pageNumbers": [1, 2, 3], "markdown": "...page 1...\n\n---\n\n...page 2...", "characters": 9000, "words": 1400}`. With `chunkWords` a row is a page row plus `chunkIndex` and `chunks`.

A single-line page with body-size text (like the sample) comes out as a plain paragraph; pages with larger titles get `#` and `##` headings.

### Pricing

Pay per result: **$2.00 per 1,000 results** ($0.002 per result). You only pay for results the Actor returns. Set a maximum total charge on the run to cap your spend.

**Each dataset row is one result.** What a row is depends on the output mode:

- Page mode (default): one result per page with text.
- Page mode with `chunkWords`: one result per chunk, so a page split into 3 chunks costs 3 results.
- Document mode: one result per PDF, however many pages it has.

Pages without text and links that fail are not charged.

### Limits

- Text layer only, no OCR: scanned pages without text are skipped and not charged.
- Tables come out as plain text lines, not Markdown tables. Images are not extracted.
- Headings are guessed from font size, so unusual layouts may get fewer or extra headings.
- Multi-column pages are read line by line across the page, so columns can be interleaved.
- Files up to 50 MB. Links must point straight at the PDF file (the response must start with `%PDF-`); redirects are followed.
- Encrypted (protected) PDFs cannot be opened and are skipped.

### FAQ

**How much does a 100-page PDF cost?**
In page mode at most 100 results, so $0.20. In document mode it is 1 result, so $0.002. With chunks it is one result per chunk. Pages without text are not counted. Set **Max results** or a maximum total charge to cap a run.

**Why did my link fail with "did not return a PDF file"?**
The link opens a web page or an online viewer, not the file. Open the PDF in your browser, copy the address of the file itself (it usually ends in `.pdf`), and use that.

**Can it read scanned PDFs?**
No. It reads the text that is stored in the PDF. Scanned pages are images; run OCR on them first, then convert the result.

**Can I get one row per file instead of per page?**
Yes. Set `outputMode` to `"document"`. All selected pages are joined in page order, separated by a `---` line, and `pageCount` tells you how many pages were joined.

**What happens with an invalid page range?**
A range such as `"5-2"` or `"abc"` stops the run before anything is fetched, with a clear error, and nothing is charged.

**Can I run it on a schedule or from my own code?**
Yes. Use Apify schedules for recurring runs, or start runs and read the dataset through the Apify API, the JavaScript or Python client, or integrations such as Make, Zapier and webhooks.

### Legal and data

This Actor processes only what you give it or public, non-personal technical data. It does not log in anywhere, does not collect personal data, and does not bypass paywalls, CAPTCHAs or access controls. You are responsible for having the right to process the inputs you provide.

### Support

Open an issue on the Issues tab. Issues are answered within 14 days. Please include the run ID and, if you can, a link to the PDF that gave the wrong result.

Other Plain Pipe document tools: DOCX to Markdown & Text Converter, EPUB to Markdown & Text Converter, PDF Merge & Split Tool, PDF Watermark & Page Number Tool, Markdown to HTML Converter, Markdown Table Converter (CSV, JSON, HTML).

Built and maintained by Plain Pipe.

# Actor input Schema

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

Direct links to PDF files (http:// or https://), one per line. The link must return the .pdf file itself, not a viewer page. Example: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf

## `pdfFile` (type: `string`):

Optional: one PDF file uploaded from your computer, used instead of or in addition to the links. Up to 50 MB.

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

Which pages to convert in every file: page numbers and ranges separated by commas, counting from 1. Leave empty for all pages. Example: 1-3,7. Pages above a file's page count are ignored.

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

"page" = one result per page (default). "document" = one result per PDF with all selected pages joined by a "---" line, plus pageCount. Each row is one billed result, so document mode costs 1 result per PDF.

## `chunkWords` (type: `integer`):

Page mode only. 0 = off (default). When set, long pages are split into chunks of about this many words, cut between paragraphs, one row per chunk with chunkIndex. Each chunk is one billed result. Example: 300 for RAG embeddings.

## `maxItems` (type: `integer`):

Stop after this many results (rows) in total, across all files. You are charged per result. Default 1000. Example: 20 to try the first pages of a long file.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "pages": "",
  "outputMode": "page",
  "chunkWords": 0,
  "maxItems": 5
}
```

# 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://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ],
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("plainpipe/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://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("plainpipe/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://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "maxItems": 5
}' |
apify call plainpipe/pdf-to-markdown --silent --output-dataset

```

## MCP server setup

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