# PDF to Markdown and Text: Tables, Pages, Metadata for LLMs (`itayozer9/pdf-to-markdown`) Actor

Convert PDF links or uploads to clean Markdown and plain text for LLMs and RAG: ruled tables as Markdown tables, headings, per-page text, metadata, page ranges, and scanned pages flagged. Pay per PDF.

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

## Pricing

from $3.00 / 1,000 pdf 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 and Text: Tables, Pages, Metadata for LLMs

Give it PDF links or upload PDF files, and get back clean Markdown and plain text, ready for an LLM prompt, a RAG pipeline or a search index. Ruled tables come out as Markdown tables, big titles as headings, and every page also comes on its own with its own text.

It runs on your files only. Nothing is scraped, there are no logins and no proxies, so runs are quick and cheap: $0.003 per PDF of up to 50 pages.

### What you get

For every PDF, one result with:

- **Markdown** of the whole document: headings, paragraphs, bullet lists and ruled tables as Markdown tables. Pages are marked with `<!-- page N -->` comments, so you can split or cite by page.
- **Plain text** of the whole document, with table rows as tab-separated lines.
- **Per-page output** (optional): Markdown, text, table count and a scanned flag for each page.
- **Metadata**: page count, title, author, subject, keywords, the app that made the file, creation and modification dates (ISO 8601).
- **Scanned pages flagged**: pages that are only an image (no text layer) are listed in `scannedPages`, and `needsOcr` is true. You know at once which files need OCR, instead of getting empty text back without a warning.
- **Counts**: words, characters, tables.
- **Errors per file**: a broken link, a password-protected PDF or a file that is not a PDF gets a row with an `error` message and is not charged. The other files in the run carry on.

### How to use it

1. Paste one or more PDF links into **PDF URLs**, or upload files under **Upload PDFs**. (The sample link is a public IRS form. Remove it if you only upload files.)
2. Optional: set **Pages** (for example `1-3, 7, 10-`), **Max pages per PDF**, or turn off **Include per-page output** for smaller results.
3. Run it. Download the results as JSON, CSV or Excel, or read them through the API.

### Input

| Field | Required | Example | Notes |
|---|---|---|---|
| `urls` | one of `urls` or `files` | `["https://example.com/report.pdf"]` | Up to 1,000 links per run |
| `files` | one of `urls` or `files` | uploaded in the Console | Stored in your own Apify key-value store |
| `pages` | no | `"1-3, 7, 10-"` | Pages to convert in every PDF. Empty = all pages |
| `maxPagesPerPdf` | no | `500` | 1 to 2,000. Later pages are skipped and the result says `truncated: true` |
| `includePages` | no | `true` | Adds the `pages` list |

```json
{
    "urls": [
        "https://www.irs.gov/pub/irs-prior/fw9--2024.pdf"
    ],
    "pages": "1-3",
    "includePages": true
}
```

### Output example

One dataset item per PDF (here a small 3-page test file with a table and one scanned page; `pages` is cut to two entries):

```json
{
    "url": "https://example.com/reports/quarterly-report.pdf",
    "fileName": "quarterly-report.pdf",
    "fileSizeBytes": 1991,
    "pageCount": 3,
    "pagesConverted": 3,
    "pageNumbers": [1, 2, 3],
    "truncated": false,
    "title": "Quarterly Report",
    "author": null,
    "createdAt": "2026-01-01T12:00:00+02:00",
    "markdown": "<!-- page 1 -->\n\n# Quarterly Report\n\nSales grew in every region this quarter.\nThe table below lists units sold.\n\n- Online orders doubled\n\n| Region | Q1 | Q2 |\n| --- | --- | --- |\n| North | 10 | 12 |\n| South | 7 | 9 |\n\n## Summary\n\nNorth sold the most units.\n\n<!-- page 2 (scanned image, no text layer) -->\n\n<!-- page 3 -->\n\nEnd of report.",
    "text": "Quarterly Report\nSales grew in every region this quarter.\nThe table below lists units sold.\n• Online orders doubled\nRegion\tQ1\tQ2\nNorth\t10\t12\nSouth\t7\t9\nSummary\nNorth sold the most units.\n\n\n\nEnd of report.",
    "wordCount": 37,
    "charCount": 203,
    "tableCount": 1,
    "scannedPages": [2],
    "needsOcr": true,
    "unreadablePages": [],
    "pages": [
        {
            "pageNumber": 1,
            "markdown": "# Quarterly Report\n\nSales grew in every region this quarter.\n...",
            "text": "Quarterly Report\nSales grew in every region this quarter.\n...",
            "tableCount": 1,
            "charCount": 185,
            "isScanned": false
        },
        {
            "pageNumber": 2,
            "markdown": "",
            "text": "",
            "tableCount": 0,
            "charCount": 0,
            "isScanned": true
        }
    ],
    "error": null,
    "convertedAt": "2026-10-31T16:00:00.000000+00:00"
}
```

Metadata the file does not have is `null`, never guessed.

### Pricing

Pay per PDF, nothing per page for normal documents:

| Event | Price | When |
|---|---|---|
| PDF converted | $0.003 | Each PDF converted, up to 50 pages |
| Extra 50 pages | $0.003 | Each further block of up to 50 pages in a longer PDF |
| Actor start | $0.00005 | Apify's standard start event, once per run |

Examples: 1,000 PDFs of up to 50 pages cost $3.00. One 120-page PDF costs $0.009 (50 pages, then 2 blocks of 50). Files that fail are not charged, a PDF with no text at all (only scanned or blank pages) is not charged, and scanned pages do not count toward the extra blocks.

Before each PDF the actor checks your run's maximum charge. If the next PDF would go over it, the run stops there; if only part of a long PDF fits, it converts the pages that fit and marks the result `truncated`. A run never goes over the limit you set.

### Limits

- **Text PDFs only, no OCR.** Scanned pages are detected and listed, not read. Run those pages through an OCR tool.
- **Tables need ruling lines.** Tables drawn with cell borders become Markdown tables. Tables laid out with spacing only (common in academic papers) come out as text lines, in reading order. Very dense grids (more than about 1,500 line crossings on one page, such as graph paper or a tiny-print sheet of 80 rows by 30 columns) and chart-heavy pages also come out as text lines, so a page never stalls the run.
- **Headings come from font size.** Text clearly larger than the body becomes `#` or `##`, bold text a little larger becomes `###`. PDFs have no real heading tags, so this is a best guess.
- **Reading order** follows the order the PDF draws its text, which is right for most single and multi-column documents. Rotated text (margin stamps, chart labels) is left out when most of the page is upright.
- Files up to 100 MB, up to 2,000 pages per PDF, up to 1,000 PDFs per run.
- Links must be public `http` or `https` URLs. The actor respects each site's robots.txt (RFC 9309): if a site disallows automated access to a path, that file gets an error row and no charge.
- Speed: a 6-page form takes about 4 seconds, a 113-page document about 8 seconds on the default memory. For big batches, give the run more memory (up to 1 GB): it finishes faster at the same price, since you pay per PDF.

### FAQ

**Is this good input for ChatGPT, Claude or a RAG pipeline?**
Yes, that is what it is for. Markdown keeps headings, lists and tables that plain text loses, and the page markers let you chunk by page and cite page numbers.

**What happens to scanned PDFs?**
You get the metadata, `needsOcr: true` and the list of scanned pages, so you can route only those files to OCR. Pages of the same file that do have text are converted as usual. A PDF with no text at all is listed but not charged.

**Can I convert only some pages?**
Yes. Set **Pages**, for example `1-5` or `2, 4, 10-`. You are charged by the pages converted, not by the size of the file.

**Can I call it from code, a schedule or an AI agent?**
Yes. Use the Apify API, a schedule, an integration such as Make or Zapier, or an MCP client. It runs with limited permissions and needs no access to your other Apify data.

**Do you keep my files?**
The actor reads each file during the run and writes the results to your own run storage. Uploaded files stay in your own key-value store, under your control.

**Is there personal data in the results?**
Only what is in your own documents. The actor does not collect anything from anywhere else.

**Something looks wrong or a file failed. What do I do?**
Open an issue on the Issues tab with your run link and, if you can share it, the PDF. Issues are answered within a few days.

# Actor input Schema

## `urls` (type: `array`):

Links to PDF files, one per line (up to 1,000 per run). The sample link is a public IRS form: replace it with your own.

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

Or upload PDF files from your computer. They are stored in your own Apify key-value store.

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

Which pages to convert, for example 1-3, 7, 10- (page 10 to the end). Empty = all pages. Applies to every PDF.

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

Pages after this many are skipped and the result is marked truncated (1 to 2,000).

## `includePages` (type: `boolean`):

Add a pages list with the Markdown, text, table count and scanned flag of each page. Turn off for smaller results.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-prior/fw9--2024.pdf"
  ],
  "maxPagesPerPdf": 500,
  "includePages": true
}
```

# Actor output Schema

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

One item per PDF: Markdown, text, metadata and pages.

# 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 = {
    "urls": [
        "https://www.irs.gov/pub/irs-prior/fw9--2024.pdf"
    ]
};

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

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

```

## MCP server setup

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