# PDF to Markdown RAG Chunks (`poyraz_demir/pdf-markdown-rag-chunks`) Actor

Turn text-layer PDFs into deterministic Markdown chunks with stable document, page and bounding-box references for retrieval and AI workflows.

- **URL**: https://apify.com/poyraz_demir/pdf-markdown-rag-chunks.md
- **Developed by:** [Poyraz Demir](https://apify.com/poyraz_demir) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 converted pdf pages

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 RAG Chunks

Turn text-layer PDFs into deterministic Markdown chunks with stable document, page and bounding-box references for retrieval and AI workflows.

### What is PDF to Markdown RAG Chunks?

PDF to Markdown RAG Chunks converts text-based PDF documents into page-local Markdown and plain-text chunks. Text is grouped into paragraphs, and columns are read one after another instead of line by line across the page. Each chunk keeps a stable document ID, page number, chunk ID and source bounding box, so a retrieval pipeline can point an answer back to the exact part of the original document.

The Actor accepts public HTTP and HTTPS PDF URLs or base64 PDF data. It processes up to 10 documents in one run. A damaged or unsupported document returns a structured error without discarding chunks from the other documents.

### How to use it

1. Add one or more PDF sources to `sources`. The example input contains a small synthetic PDF, so you can run it as it is.
2. For a URL, use a publicly accessible PDF that you are allowed to process. For base64 input, include the encoded bytes and a filename.
3. Set the maximum file size, page limit and chunk size for your pipeline.
4. Run the Actor and read one dataset record per chunk. Export the dataset as JSON, JSONL, CSV or another format supported by Apify.

Chunks never cross page boundaries. `documentId` is derived from the PDF SHA-256, and `chunkId` includes the document, page and chunk position. Repeating the same input produces the same IDs and text.

### Input and output example

Input:

```json
{
  "sources": [
    {"url": "https://example.com/report.pdf"},
    {"base64": "JVBERi0xLjQK...", "filename": "notes.pdf"}
  ],
  "maxBytes": 20000000,
  "maxPages": 200,
  "chunkChars": 1800,
  "respectRobotsTxt": true
}
```

A successful record has this shape:

```json
{
  "documentId": "b2e6f24198dabef8fcc31f5f",
  "source": "base64:synthetic.pdf",
  "filename": "synthetic.pdf",
  "page": 1,
  "pageCount": 1,
  "chunkId": "b2e6f24198dabef8fcc31f5f:p0001:c0001",
  "markdown": "Synthetic PDF RAG chunk",
  "text": "Synthetic PDF RAG chunk",
  "bbox": [72.0, 62.484, 148.692, 90.484],
  "status": "success",
  "error": null
}
```

A failed document returns one record with `status: "error"` and an error code instead of chunks.

### Document access and limits

This version reads the existing text layer. It does not perform OCR, reconstruct tables, detect headings or interpret formulas, and unusual layouts can still be read in an unexpected order. Scanned documents without a text layer return `NO_TEXT_LAYER`. Encrypted PDFs return `ENCRYPTED_PDF`. Each document can be up to 20 MB and 200 pages, and each chunk holds 500 to 4000 characters.

Each run processes only the submitted documents. There is no site discovery or crawling. URL downloads reject local and non-public network destinations, validate redirects and can enforce robots.txt rules. The Actor does not log in, bypass paywalls or DRM, solve CAPTCHAs or use your cookies.

Use the Actor only for documents you are allowed to access and process. Stop downloading files from a site if its terms or robots.txt rules disallow it, or if the site owner objects. You are responsible for how you use the extracted content and for complying with copyright and applicable law.

### Pricing

Pay-per-event pricing: $0.0015 per Actor start plus $0.0005 per converted page. A page is charged once when it produces at least one text chunk, however many chunks it contains. Failed documents, blank pages and documents rejected by limits are not charged. If a run reaches your maximum cost per run, the Actor finishes the page it has already charged for and stops before the next page.

### Troubleshooting and support

- `NOT_PDF`: check that the source is a valid PDF rather than an HTML download page.
- `NO_TEXT_LAYER`: use a text-based PDF; OCR is not included.
- `PDF_TOO_LARGE` or `RESPONSE_TOO_LARGE`: reduce the file or adjust `maxBytes` within the documented limit.
- `TOO_MANY_PAGES`: split the document or adjust `maxPages` within the documented limit.
- `INVALID_BASE64`: supply the raw base64 PDF bytes without a data-URL prefix.
- `ROBOTS_DISALLOWED`: the site's robots rules do not permit downloading this file.

For bug reports, use Actor Issues in Apify Console. Include the run ID and the returned error code. Do not post private documents, credentials or personal data.

# Actor input Schema

## `sources` (type: `array`):

One to ten PDF sources, each with either a public url or base64 data and filename.

## `timeoutSecs` (type: `integer`):

Shared budget for robots, redirects and response reading; system DNS is checked immediately afterwards.

## `maxBytes` (type: `integer`):

Limit applied after base64 decoding and to streamed URL downloads.

## `maxPages` (type: `integer`):

Reject a document whose total page count exceeds this limit.

## `chunkChars` (type: `integer`):

Chunks stay on one page and split oversized blocks at words, then characters if needed.

## `respectRobotsTxt` (type: `boolean`):

Check robots rules for URL sources and every redirect target.

## Actor input object example

```json
{
  "sources": [
    {
      "base64": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3MSA+PgpzdHJlYW0KQlQgL0YxIDEyIFRmIDcyIDcyMCBUZCAxNiBUTAooU3ludGhldGljIFBERikgVGogVCoKKFJBRyBjaHVuaykgVGogVCoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZyA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAwNjQgMDAwMDAgbiAKMDAwMDAwMDEyMSAwMDAwMCBuIAowMDAwMDAwMjQ3IDAwMDAwIG4gCjAwMDAwMDAzNjggMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0NjUKJSVFT0YK",
      "filename": "synthetic.pdf"
    }
  ],
  "timeoutSecs": 10,
  "maxBytes": 20000000,
  "maxPages": 200,
  "chunkChars": 1800,
  "respectRobotsTxt": true
}
```

# 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 = {
    "sources": [
        {
            "base64": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3MSA+PgpzdHJlYW0KQlQgL0YxIDEyIFRmIDcyIDcyMCBUZCAxNiBUTAooU3ludGhldGljIFBERikgVGogVCoKKFJBRyBjaHVuaykgVGogVCoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZyA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAwNjQgMDAwMDAgbiAKMDAwMDAwMDEyMSAwMDAwMCBuIAowMDAwMDAwMjQ3IDAwMDAwIG4gCjAwMDAwMDAzNjggMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0NjUKJSVFT0YK",
            "filename": "synthetic.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("poyraz_demir/pdf-markdown-rag-chunks").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 = { "sources": [{
            "base64": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3MSA+PgpzdHJlYW0KQlQgL0YxIDEyIFRmIDcyIDcyMCBUZCAxNiBUTAooU3ludGhldGljIFBERikgVGogVCoKKFJBRyBjaHVuaykgVGogVCoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZyA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAwNjQgMDAwMDAgbiAKMDAwMDAwMDEyMSAwMDAwMCBuIAowMDAwMDAwMjQ3IDAwMDAwIG4gCjAwMDAwMDAzNjggMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0NjUKJSVFT0YK",
            "filename": "synthetic.pdf",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("poyraz_demir/pdf-markdown-rag-chunks").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 '{
  "sources": [
    {
      "base64": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvUmVzb3VyY2VzIDw8IC9Gb250IDw8IC9GMSA1IDAgUiA+PiA+PiAvQ29udGVudHMgNCAwIFIgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL0xlbmd0aCA3MSA+PgpzdHJlYW0KQlQgL0YxIDEyIFRmIDcyIDcyMCBUZCAxNiBUTAooU3ludGhldGljIFBERikgVGogVCoKKFJBRyBjaHVuaykgVGogVCoKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSAvRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZyA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAwNjQgMDAwMDAgbiAKMDAwMDAwMDEyMSAwMDAwMCBuIAowMDAwMDAwMjQ3IDAwMDAwIG4gCjAwMDAwMDAzNjggMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgo0NjUKJSVFT0YK",
      "filename": "synthetic.pdf"
    }
  ]
}' |
apify call poyraz_demir/pdf-markdown-rag-chunks --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,poyraz_demir/pdf-markdown-rag-chunks"
        }
    }
}
```

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/3CXUgXs2JYBPXV40e/builds/DGS8jokCllBkm0Ci7/openapi.json
