# PDF to Markdown for LLM & RAG (CJK-ready) (`masahishop/pdf-to-markdown`) Actor

Convert PDFs to clean Markdown, plain text or RAG chunks. Handles two-column papers, running headers/footers, footnotes, and Japanese/Chinese/Korean text including vertical writing. Pay only for successfully processed files.

- **URL**: https://apify.com/masahishop/pdf-to-markdown.md
- **Developed by:** [Masahi Shop](https://apify.com/masahishop) (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 $0.26 / 1,000 page processeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 for LLM & RAG (CJK-ready)

Turn PDF links into **clean Markdown, plain text, or ready-to-embed RAG chunks** — including documents that most extractors get wrong: **two-column papers, Japanese / Chinese / Korean text, and vertical writing (縦書き)**.

You pay only for PDFs that were processed successfully. Broken links, non-PDF URLs and oversized files are reported but never charged.

### What can it be used for?

- **Feed PDFs to ChatGPT, Claude or any LLM** without broken line breaks, page numbers and running headers in the middle of sentences.
- **Build a RAG / vector database** from reports, manuals or research papers. Each chunk keeps the page numbers it came from, so your app can cite sources.
- **Process Japanese, Chinese and Korean PDFs**: lines are joined without inserting spaces between characters, letter-spaced titles are fixed, and chunks end at `。！？` sentence endings.
- **Read academic papers**: two-column layouts are read column by column, not across.
- **Call it from AI agents** via the Apify API or MCP.

### Why this Actor?

| Problem in typical PDF extractors | What this Actor does |
|---|---|
| Two columns are mixed line by line | Detects the column gutter and reads left column, then right |
| `日本 語` — spaces inserted between CJK characters at line breaks | Joins CJK lines without spaces; keeps spaces in English |
| `平 成 2 9 年` letter-spaced headings | Collapses letter-spacing → `平成29年` |
| Vertical Japanese text comes out scrambled | Reads columns right-to-left, top-to-bottom |
| Page numbers, running headers and footnote markers inside sentences | Removes repeated headers/footers and page numbers; footnote markers are dropped and footnotes are kept after the paragraph |
| A paragraph split by a page break becomes two fragments | Joins paragraphs across page breaks |
| Chunks cut in the middle of a word or sentence | Sentence-aware chunking with overlap, for both English and CJK |

### Input

| Field | Description |
|---|---|
| `pdfUrls` | List of direct links to PDF files. Only these URLs are downloaded. |
| `outputFormat` | `markdown` (default), `text`, or `chunks` |
| `pageRange` | e.g. `1-5, 8, 12-` (empty = all pages) |
| `chunkSize` / `chunkOverlap` | Chunk length and overlap in characters (for `chunks`) |
| `includePages` | Also return each page separately |
| `removeHeadersFooters` | Remove running headers, footers and page numbers (default on) |
| `normalizeWidth` | Convert full-width letters/digits `ＡＢＣ１２３` → `ABC123` |
| `password` | Password for encrypted PDFs |
| `maxPagesPerPdf`, `maxFileSizeMb`, `downloadTimeoutSecs`, `maxConcurrency` | Limits |

Example:

```json
{
    "pdfUrls": ["https://arxiv.org/pdf/1706.03762"],
    "outputFormat": "chunks",
    "chunkSize": 1000,
    "chunkOverlap": 100
}
```

### Output

One dataset item per PDF.

```json
{
    "url": "https://arxiv.org/pdf/1706.03762",
    "fileName": "1706.03762",
    "status": "success",
    "pageCount": 15,
    "processedPages": 15,
    "language": "latin",
    "hasVerticalText": false,
    "likelyScannedPages": [],
    "metadata": {
        "title": null,
        "author": null,
        "creator": "LaTeX with hyperref",
        "producer": "pdfTeX-1.40.25",
        "creationDate": "2024-04-10T21:11:43.000Z",
        "pdfVersion": "1.5"
    },
    "charCount": 39593,
    "markdown": "Provided proper attribution is provided, Google hereby grants permission ...\n\n### Attention Is All You Need\n\n...",
    "warnings": [],
    "processingTimeMs": 15280
}
```

With `outputFormat: "chunks"`, `markdown` is replaced by:

```json
"chunkCount": 48,
"chunks": [
    { "index": 0, "pageStart": 1, "pageEnd": 1, "charCount": 852, "text": "Provided proper attribution is provided, ..." }
]
```

`status` is one of `success`, `no_text` (the PDF contains only images), or `failed` (with an `error` message).

### Pricing

Pay per event — no subscription, platform usage included:

| Event | Price |
|---|---|
| PDF processed (per successfully processed PDF) | $0.003 ($3.00 / 1,000 PDFs) |
| Page processed (per processed page) | $0.0003 ($0.30 / 1,000 pages) |
| Actor start (per run, Apify standard) | $0.00005 |

Example: a 20-page PDF costs about $0.009.

Failed downloads, non-PDF URLs and files over the size limit are not charged. You can always cap the cost of a run with the maximum charge setting in Apify.

### Limitations

- **Scanned PDFs (images only) are not OCR'd.** They are reported with `status: "no_text"` and the page numbers in `likelyScannedPages`. For those, use [OCR Japanese, Chinese, Korean & English](https://apify.com/masahishop/ocr-cjk).
- Tables are output as text lines, not as Markdown tables.
- Headings are detected from font size. Headings that only differ by bold weight are output as normal paragraphs.
- Layout analysis is heuristic: unusual magazine-style layouts may come out in a slightly different order.

### Privacy

The Actor only downloads the URLs you provide. PDF contents are processed in memory and written only to your own run's dataset. Please make sure you have the right to process the documents you submit.

# Actor input Schema

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

Direct links to PDF files. Only the URLs you list are downloaded - the Actor never crawls other pages.

## `outputFormat` (type: `string`):

<b>markdown</b>: headings, lists and paragraphs (best for LLMs). <b>text</b>: plain text. <b>chunks</b>: sentence-aware chunks with page numbers, ready for a vector database.

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

Pages to process, e.g. <code>1-5, 8, 12-</code>. Leave empty for all pages.

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

Also return the content of each page separately.

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

Target chunk length in characters (only for RAG chunks). Chunks end at sentence boundaries, including Japanese/Chinese sentence endings.

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

Characters repeated between neighbouring chunks (max. half of the chunk size).

## `removeHeadersFooters` (type: `boolean`):

Removes small lines repeated at the top/bottom of most pages, and bare page numbers.

## `normalizeWidth` (type: `boolean`):

Converts full-width letters and digits (ＡＢＣ１２３) to normal width (ABC123). Useful for Japanese documents before search or embedding.

## `password` (type: `string`):

Password for encrypted PDFs (used for all URLs in this run).

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

Safety limit on pages processed per file.

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

Larger files are skipped (and not charged).

## `downloadTimeoutSecs` (type: `integer`):

Timeout for downloading one file.

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

How many PDFs are processed at the same time.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://arxiv.org/pdf/1706.03762",
    "https://digital.go.jp/assets/contents/node/basic_page/field_ref_resources/f7fde41d-ffca-4b2a-9b25-94b8a701a037/f1e42cee/20240705_resources_data_guideline_01.pdf"
  ],
  "outputFormat": "markdown",
  "includePages": false,
  "chunkSize": 1000,
  "chunkOverlap": 100,
  "removeHeadersFooters": true,
  "normalizeWidth": false,
  "maxPagesPerPdf": 500,
  "maxFileSizeMb": 50,
  "downloadTimeoutSecs": 60,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

All converted PDFs (Markdown / text / chunks, metadata, status).

## `overview` (type: `string`):

Status, page count, language and warnings for each PDF.

# 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",
        "https://digital.go.jp/assets/contents/node/basic_page/field_ref_resources/f7fde41d-ffca-4b2a-9b25-94b8a701a037/f1e42cee/20240705_resources_data_guideline_01.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("masahishop/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",
        "https://digital.go.jp/assets/contents/node/basic_page/field_ref_resources/f7fde41d-ffca-4b2a-9b25-94b8a701a037/f1e42cee/20240705_resources_data_guideline_01.pdf",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("masahishop/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",
    "https://digital.go.jp/assets/contents/node/basic_page/field_ref_resources/f7fde41d-ffca-4b2a-9b25-94b8a701a037/f1e42cee/20240705_resources_data_guideline_01.pdf"
  ]
}' |
apify call masahishop/pdf-to-markdown --silent --output-dataset

```

## MCP server setup

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