# PDF to Markdown, Text & Tables (with OCR) (`pinkish_armada/pdf-to-markdown`) Actor

Convert PDFs from URLs into clean Markdown for LLMs and RAG: headings, paragraphs, lists, ruled tables as Markdown and JSON, two-column layouts, metadata, optional chunks. Scanned pages are OCR'd automatically.

- **URL**: https://apify.com/pinkish_armada/pdf-to-markdown.md
- **Developed by:** [Phuc Tiet](https://apify.com/pinkish_armada) (community)
- **Categories:** AI, Developer tools
- **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 to Markdown, Text & Tables (with OCR)

Turn PDFs into **clean Markdown that LLMs, RAG pipelines and humans can use**: headings, paragraphs, bullet and numbered lists, **ruled tables as Markdown tables and JSON**, document metadata and page markers. Scanned pages are **OCR'd automatically**. Optional **RAG chunks** with heading and page range.

Paste PDF URLs, click Start, and download the results as JSON, CSV or Excel, or use them via API, MCP, Make, Zapier or n8n.

### Why this one

- **Pay per page, not per file.** A 1-page invoice costs a tenth of a cent, so you never overpay for small files. Long files are capped by `maxPagesPerDocument`.
- **Real Markdown structure.** Headings are detected from font size and weight, lists are recognised, hyphenated line breaks are re-joined, two-column layouts are read column by column, and rotated margin stamps are dropped.
- **Tables you can use.** Ruled tables are placed in the Markdown where they appear, and are also returned as `tables[].rows` (JSON arrays).
- **OCR only where needed.** In `auto` mode only pages without a text layer (scans) are OCR'd with Tesseract, in English, German, French, Spanish, Italian, Portuguese or Dutch.
- **RAG-ready.** `chunkSize` returns paragraph-aligned chunks with `heading`, `pageStart` and `pageEnd`, so answers can cite pages.
- **Robust.** Retries with backoff, file-size limits, and clear error codes (`HTTP_404`, `NOT_A_PDF`, `PASSWORD_REQUIRED`, `TOO_LARGE`, `TIMEOUT`...). One bad file never stops the run, and **failed files are not charged**.

### Input

| Field | Default | Description |
|---|---|---|
| `documentUrls` | – | Direct PDF links (required). Up to 5,000 per run. |
| `ocrMode` | `auto` | `auto` (scanned pages only), `off`, `force`. |
| `ocrLanguage` | `eng` | `eng`, `deu`, `fra`, `spa`, `ita`, `por`, `nld`, or combinations like `eng+deu`. |
| `extractTables` | true | Markdown tables in place + JSON `tables`. |
| `maxPagesPerDocument` | 300 | 0 = all pages. |
| `chunkSize` / `chunkOverlap` | 0 / 200 | RAG chunks in characters (0 = off). |
| `includePlainText` | false | Add a plain-text version. |
| `pageMarkers` | true | `<!-- page N -->` comments in the Markdown. |
| `pdfPassword` | – | For encrypted PDFs (stored as a secret). |
| `maxFileSizeMb` | 100 | Larger files are skipped (not charged). |

```json
{
  "documentUrls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"],
  "chunkSize": 2000
}
```

### Output

One dataset row per document:

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "status": "ok",
  "fileName": "fw9.pdf",
  "pageCount": 6,
  "pagesProcessed": 6,
  "ocrPages": 0,
  "title": "Form W-9 (Rev. March 2024)",
  "author": "SE:W:CAR:MP",
  "creationDate": "2024-03-06T08:18:13",
  "wordCount": 6251,
  "tableCount": 1,
  "markdown": "<!-- page 1 -->\n\n# W-9 Request for Taxpayer ...",
  "tables": [{ "page": 3, "index": 0, "rows": [["IF the entity/individual on line 1 is a(n) . . .", "THEN check the box for . . ."], ["• Corporation", "Corporation."]], "markdown": "| IF the entity ... |" }],
  "chunks": [{ "index": 0, "text": "...", "heading": "W-9 Request for Taxpayer", "pageStart": 1, "pageEnd": 1, "chars": 1730 }],
  "warnings": []
}
```

Failed documents get `"status": "error"` with `errorCode` and `error`. Very large results (over ~8 MB) are saved in full to the key-value store and linked from `fullResultUrl`. A run summary is saved as `OUTPUT`.

### How much does it cost to convert PDF to Markdown?

Pay per event, plus a tiny Actor start fee ($0.00005 per GB of memory):

- **Page converted** (text layer): $0.001 per page ($1.00 per 1,000 pages).
- **Page OCR'd** (scanned page, or any page with `ocrMode: force`): $0.004 per page ($4.00 per 1,000 pages).

Platform usage is included in the price. Failed downloads, non-PDF files and pages beyond `maxPagesPerDocument` are **not charged**. If you set a maximum cost per run, the Actor processes only the pages that fit within it.

### Limitations (honest)

- It is a fast, rule-based converter (pdfplumber + Tesseract), not a vision-language model. It works best on born-digital PDFs such as reports, papers, forms, contracts, manuals and invoices.
- Tables are detected when they have ruling lines. Borderless tables and booktabs-style tables (only horizontal rules) come out as text lines, not Markdown tables.
- Math formulas, sub/superscripts and complex figure labels are kept as plain text and may be fragmented.
- OCR quality depends on scan quality. Handwriting is not supported.
- Only PDF input. The URL must return the file itself, not an HTML viewer page.
- Process only documents you have the right to use. Files are processed inside your run and are not stored anywhere else.

### FAQ

**Can I upload a file instead of a URL?** Upload it to an Apify key-value store, Dropbox (use a `?dl=1` link), S3 or similar, and pass the direct link.

**Encrypted PDF?** Set `pdfPassword`. Without it the document fails with `PASSWORD_REQUIRED` and is not charged.

**How fast is it?** Typically 0.05–0.3 s per text page and 2–4 s per OCR page.

### Use it from AI agents (MCP), the API, Make, Zapier and n8n

- **AI agents and MCP clients** (Claude, ChatGPT, Cursor, VS Code): add `https://mcp.apify.com?tools=pinkish_armada/pdf-to-markdown` as an MCP server, and your agent can convert PDFs to Markdown, text and tables as a tool call.
- **API**: run it and get the results in one request:

```bash
curl -X POST "https://api.apify.com/v2/acts/pinkish_armada~pdf-to-markdown/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"documentUrls":["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]}'
```

- **No-code**: use the official Apify apps for **Make**, **Zapier** and **n8n** ("Run an Actor"), then send results to Google Sheets, Slack, Airtable or your database. You can also schedule runs in Apify Console and add a webhook or email integration.

### Related tools

- [Sitemap URL Extractor](https://apify.com/pinkish_armada/sitemap-url-extractor): list every URL of a site from its sitemaps, with optional 404 checks
- [Broken Link Checker](https://apify.com/pinkish_armada/broken-link-checker): crawl a website and report broken links, 404s and redirects
- [Office Docs to Markdown](https://apify.com/pinkish_armada/office-to-markdown): turn DOCX, PPTX, XLSX, HTML and EPUB into clean Markdown for LLMs and RAG
- [Domain Lookup](https://apify.com/pinkish_armada/domain-intel): bulk RDAP WHOIS, DNS, SPF/DMARC and SSL-expiry checks for domains

# Actor input Schema

## `documentUrls` (type: `array`):

Direct links to PDF files (public URLs, signed S3/GCS links, Dropbox <code>?dl=1</code> links, Apify key-value store record URLs, ...). Up to 5,000 per run.

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

<b>auto</b>: OCR only pages that have no text layer (scans). <b>force</b>: OCR every page. <b>off</b>: never OCR. OCR pages are priced separately.

## `ocrLanguage` (type: `string`):

Language of scanned pages: eng, deu, fra, spa, ita, por, nld. Combine with + (e.g. <code>eng+deu</code>).

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

Detect ruled tables, render them as Markdown tables in place and also return them as JSON rows in <code>tables</code>.

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

Process at most this many pages of each PDF (0 = all pages). You pay per processed page, so this caps cost for very long files.

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

If set (e.g. 2000), also return <code>chunks</code>: Markdown split on paragraph boundaries with heading and page range, ready for embedding. 0 = off.

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

Characters of the previous chunk repeated at the start of the next one.

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

Also return a plain-text version without Markdown syntax.

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

Insert <code>\<!-- page N --></code> comments so you can cite page numbers.

## `pdfPassword` (type: `string`):

Password for encrypted PDFs (applied to all documents in the run).

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

Larger files are skipped with an error (not charged).

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

How many documents are downloaded and converted at the same time.

## `requestTimeoutSecs` (type: `integer`):

Timeout for downloading each file (retried with backoff).

## Actor input object example

```json
{
  "documentUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "ocrMode": "auto",
  "ocrLanguage": "eng",
  "extractTables": true,
  "maxPagesPerDocument": 300,
  "chunkSize": 0,
  "chunkOverlap": 200,
  "includePlainText": false,
  "pageMarkers": true,
  "maxFileSizeMb": 100,
  "maxConcurrency": 2,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

Converted documents: markdown, title/author/dates, page counts, OCR page count, tables, chunks, warnings, or errorCode/error for failed files.

## `summary` (type: `string`):

Documents succeeded/failed and pages/OCR pages charged.

## `files` (type: `string`):

Full JSON results for documents too large for a single dataset row (document-N.json), linked from fullResultUrl.

# 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 = {
    "documentUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

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

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

```

## MCP server setup

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