# PDF to Markdown for RAG & LLMs — Tables (`vivid_astronaut/pdf-to-markdown-for-rag`) Actor

Convert PDFs into structured Markdown for RAG and LLM ingestion while preserving tables, headings, and reading order. No external parsing API key; tiered per-page pricing.

- **URL**: https://apify.com/vivid\_astronaut/pdf-to-markdown-for-rag.md
- **Developed by:** [BRAINIALL Team](https://apify.com/vivid_astronaut) (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 $3.00 / 1,000 document pages

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PDF to Markdown for RAG & LLMs — Tables and Reading Order Preserved

Turn **any PDF URL** into clean, structured **Markdown** that is ready to feed into RAG pipelines, vector databases, and LLM prompts — with **zero setup**.

Paste PDF links. Press **Start**. Get Markdown with real headings, lists, and **actual Markdown tables** instead of collapsed text soup. That's it.

**No LlamaParse key. No Reducto contract. No OpenAI account. No monthly subscription.** Usage is billed through Apify pay-per-event. The creator event rate is **$0.004 per page on Free and Bronze**, with lower rates on higher Apify plans.

***

### Why this Actor?

Every serious RAG or agent pipeline hits the same wall: PDFs. Naive extractors (pypdf, pdftotext) destroy tables, shuffle multi-column layouts, and drop heading structure — and your retrieval quality dies with them. The dedicated parsing APIs (LlamaParse, Reducto, Unstructured) fix that, but they make you sign up for **another account, another API key, another bill**.

This Actor runs on a managed document-parsing backend — the key is built in.

|                              | **This Actor**                     | LlamaParse                         | Reducto                       | DIY (pypdf & friends)        |
| ---------------------------- | ---------------------------------- | ---------------------------------- | ----------------------------- | ---------------------------- |
| Requires your own API key    | **No — works out of the box**      | Yes (LlamaCloud key + credits)     | Yes (sales contact)           | —                            |
| Price per page               | **$0.004 base rate; plan discounts** | Separate provider pricing        | Sales quote                   | Your infrastructure and engineering |
| Tables                       | **Real Markdown tables**           | Mode-dependent                     | Yes                           | Usually destroyed            |
| Headings & structure         | **Proper `#`/`##` hierarchy**      | Yes                                | Yes                           | Flat text                    |
| Setup                        | **None — paste URLs**              | Account, credits, SDK              | Contract, integration         | Code, maintenance            |
| Billing                      | **Pay per event — only pages actually converted** | Credit packs        | Enterprise contract           | —                            |

At the Free/Bronze rate, a 20-page contract costs **$0.08** and a 300-page annual report costs **$1.20**. Higher Apify plans have lower creator event rates. Failed documents are never charged.

### Reproducible superscript safety check

Scientific units can fail silently when PDF text contains Unicode superscripts or control characters. On 3 August 2026, build `1.0.10` passed BRAINIALL's one-page synthetic fixture: all five expected strings (`mg·mL⁻¹`, `kg/m³`, `m²`, `10²³ mol⁻¹`, and a mixed table cell) were present, the Markdown contained zero U+0000 NUL characters, and all 1,049 characters survived a SQLite `TEXT` round trip.

- [Download the independent PDF fixture](https://www.brainiall.com/transcreve/fixtures/pdf-markdown-superscript-safety-fixture.pdf)
- [Run the browser-side integrity scanner](https://www.brainiall.com/transcreve/tools/pdf-markdown-silent-corruption)

This is a narrow, reproducible regression check—not a claim of general PDF, OCR, formula, or table compatibility. Test your own representative documents before production use.

### What it does

1. **Downloads each PDF** from any direct URL — public links, presigned S3/GCS URLs, or files stored in an Apify key-value store. Files are validated as real PDFs before processing (an HTML viewer page instead of a raw file produces a clear error, not garbage output).
2. **Parses the document with layout awareness**: reading order is reconstructed (multi-column pages come out in the right sequence), headings become `#`/`##`/`###`, lists stay lists, and tables are emitted as GitHub-flavored Markdown tables — the single biggest quality lever for RAG over business documents.
3. **Outputs Markdown twice**: inline in the dataset item (for API consumers and agents) and as a downloadable `.md` file in the run's key-value store (for humans and downstream jobs).
4. **Keeps going on failure**: a dead link or corrupt file produces an `{ url, error }` item; the rest of the batch is processed and only successful pages are charged.

### Input

Set optional `integrationSource` to a non-secret route tag such as `weknora-gfm` when a guide, template, or partner invokes the Actor. The tag is validated (1-64 lowercase letters, numbers, `.`, `_`, or `-`) and copied to each dataset row. It is cohort evidence only: it does not identify a payer, payout, settlement, or buyer-linked revenue. Never place a token, URL, email, or personal data in it. Omit it to use `apify-store`.

```json
{
    "sources": [
        "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf",
        "https://example.com/reports/annual-report-2025.pdf"
    ],
    "maxPages": 500
}
```

| Field      | Type    | Default      | Description                                                                    |
| ---------- | ------- | ------------ | ------------------------------------------------------------------------------ |
| `sources`  | array   | *(required)* | URLs of PDF files: direct links or Apify key-value store record URLs           |
| `maxPages` | integer | `500`        | Safety cap per document; larger PDFs fail before conversion and are not charged |

### Output

One dataset item per source. Here is the result for the sample invoice above:

```json
{
    "url": "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf",
    "filename": "wordpress-pdf-invoice-plugin-sample.pdf",
    "pages": 1,
    "char_count": 731,
    "charged_pages": 1,
    "markdown": "# Invoice\n\n**From:** Sliced Invoices...",
    "files": {
        "md": "https://api.apify.com/v2/key-value-stores/<storeId>/records/001-slicedinvoices.com-pdf-wordpress-pdf-invoice-plugin.md"
    }
}
```

And the Markdown itself — note the tables survive as tables:

```markdown
## Invoice

**From:** Sliced Invoices — Suite 5A-1204, 123 Somewhere Street, Your City AZ 12345
**To:** Test Business — 123 Somewhere St, Melbourne VIC 3000

| Invoice Number | Order Number | Invoice Date     | Due Date         | Total Due |
| -------------- | ------------ | ---------------- | ---------------- | --------- |
| INV-3337       | 12345        | January 25, 2016 | January 31, 2016 | $93.50    |

| Service    | Description                          | Hrs/Qty | Rate/Price | Adjust | Sub Total |
| ---------- | ------------------------------------ | ------- | ---------- | ------ | --------- |
| Web Design | New website design and development   | 1.00    | $85.00     | 0.00%  | $85.00    |

| Sub Total | $85.00     |
| --------- | ---------- |
| Tax       | $8.50      |
| **Total** | **$93.50** |
```

That is exactly what you want in a vector store: an LLM can answer "what is the total due and when?" from this chunk directly, no layout guessing.

If a source fails (dead link, not a PDF, over the page cap), the run **keeps going** and the failed source produces:

```json
{ "url": "https://example.com/broken-link.pdf", "error": "Could not download the file: HTTP 404" }
```

### Pricing

The creator event rate depends on your Apify plan. Billing has a one-page minimum per document and uses Apify pay-per-event; no separate document-parsing subscription or external API key is required.

| Apify plan | Creator event rate per converted page |
| ---------- | ------------------------------------- |
| Free / Bronze | $0.004 |
| Silver | $0.0035 |
| Gold | $0.003 |
| Platinum | $0.0025 |
| Diamond | $0.002 |

The examples below use the **Free/Bronze $0.004 rate**. Your displayed Store price can be lower when your Apify plan qualifies for a lower event rate.

| Workload                                | Cost      |
| --------------------------------------- | --------- |
| 1-page invoice                          | $0.004    |
| 20-page contract                        | $0.08     |
| 300-page annual report                  | $1.20     |
| 1,000 × 2-page invoices                 | $8.00     |

Documents that fail to download or convert are **never charged**. PDFs above your `maxPages` safety cap fail before conversion and are not charged.

### Use it via API

Run the Actor from any language using the [Apify API](https://docs.apify.com/api/v2):

```bash
curl -X POST "https://api.apify.com/v2/acts/<username>~pdf-to-markdown-for-rag/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "sources": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"] }'
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('<username>/pdf-to-markdown-for-rag').call({
    sources: ['https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].markdown);
```

Python (drop-in for a RAG ingestion job):

```python
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("<username>/pdf-to-markdown-for-rag").call(
    run_input={"sources": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"]}
)
for item in client.dataset(run["defaultDatasetId"]).list_items().items:
    chunks = split_markdown(item["markdown"])  # your chunker
    vector_store.add(chunks, metadata={"source": item["url"]})
```

### Use it from AI agents (MCP)

This Actor works out of the box with the [Apify MCP server](https://mcp.apify.com), so agents built on Claude, ChatGPT, Cursor, or any MCP-compatible framework can call it as a tool:

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com/sse?actors=<username>/pdf-to-markdown-for-rag"
        }
    }
}
```

Your agent can turn a supported PDF into LLM-ready Markdown without managing a separate document-parsing provider key. An Apify account/token is still required to call the Actor through Apify, and pay-per-event billing charges only successfully converted pages.

### FAQ

**Do I need a LlamaParse / Reducto / OpenAI API key?**
No. That is the point of this Actor. Parsing runs on a managed backend whose credentials are bundled with the Actor. You only need your Apify account.

**Why Markdown and not plain text or JSON?**
Markdown is the lingua franca of LLM ingestion: heading hierarchy gives your chunker natural split points, tables stay machine-readable, and every major framework (LangChain, LlamaIndex, Haystack) has first-class Markdown loaders. Plain text loses structure; per-block JSON makes you rebuild it.

**How are tables handled?**
Tables are detected from the page layout and emitted as GitHub-flavored Markdown tables (pipes and header rows), not as space-aligned text. Merged or deeply nested table cells are flattened on a best-effort basis.

**Does it work on scanned PDFs?**
Digital (text-based) PDFs give the best results. Scanned pages are processed through the backend's visual document understanding, so typical scanned invoices and contracts work; very low-quality scans may reduce accuracy.

**Which languages are supported?**
The parser is language-agnostic for Latin-script languages and handles the major non-Latin scripts (CJK, Cyrillic, Arabic) as long as the PDF carries proper text or a legible scan.

**How long does a conversion take?**
Simple digital PDFs process in seconds. Heavy, scan-based, or very large documents can take up to ~20 seconds per page; each document has a generous 20-minute processing budget.

**What are the size limits?**
Up to 1 GB per file download and `maxPages` pages per document (default 500, configurable up to 10,000). Larger PDFs fail before conversion and are not charged.

**What happens when one URL in my batch is broken?**
The run continues. The broken URL produces a `{ url, error }` item in the dataset; all other documents are processed and only successful pages are charged.

**Where is my Markdown file?**
Twice in the run: inline in the dataset item (`markdown` field) and as a downloadable `.md` file in the run's key-value store — each item links to it under `files.md`.

**Can I convert DOCX, PPTX, or HTML?**
This Actor is tuned for PDF. For other formats, print/export them to PDF first (most converters do this losslessly) and pass the resulting URL.

**Is my document stored?**
Documents are processed transiently and deleted from the worker after conversion. The resulting Markdown lives in your Apify dataset/key-value store, under your control.

### Related use cases

- Build RAG knowledge bases from contracts, reports, manuals, and research papers
- Feed clean document context to AI agents (Claude, ChatGPT, custom LLM apps) via MCP
- Extract invoice and financial-statement tables into readable, diffable Markdown
- Preprocess PDF corpora for fine-tuning or embedding pipelines
- Archive PDFs as searchable, greppable, version-controllable Markdown

# Actor input Schema

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

URLs of PDF files to convert: direct links (.pdf) or files stored in an Apify key-value store.

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

Safety cap. Documents above this page count fail before conversion and are not charged.

## `integrationSource` (type: `string`):

A non-secret route tag for attribution, for example weknora-gfm. Use only lowercase letters, numbers, dots, underscores, or hyphens. It is copied to each dataset row; never put a token, URL, email, or personal data here.

## Actor input object example

```json
{
  "sources": [
    "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"
  ],
  "maxPages": 500,
  "integrationSource": "apify-store"
}
```

# Actor output Schema

## `documents` (type: `string`):

One dataset row per input URL, including bounded integrationSource attribution tag, Markdown, page count, generated-file links, or a bounded conversion error.

## `markdownFiles` (type: `string`):

Downloadable .md records written to the run's default key-value store.

# 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": [
        "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vivid_astronaut/pdf-to-markdown-for-rag").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": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("vivid_astronaut/pdf-to-markdown-for-rag").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"
  ]
}' |
apify call vivid_astronaut/pdf-to-markdown-for-rag --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=vivid_astronaut/pdf-to-markdown-for-rag",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FKKn3mlUe1Mm7LMz5/builds/1VGElWJlad2WvVt4b/openapi.json
