PDF to Markdown API for LLMs and RAG: what 1,000 pages cost
You want one dependable call that turns PDFs into Markdown or chunks with page numbers for citations, at a cost you can predict. The free self-hosted tools, the hosted APIs priced per 1,000 pages, and when each one is the right choice.
Third-party prices checked and our tool's calls run on 28 October 2026.
Retrieval over documents (RAG) starts with a parser. The parser decides what your vector store holds: clean sections with headings and page numbers, or a stream of text with page footers and broken tables mixed in. This guide covers what a RAG pipeline needs from that step, the free tools you can run yourself, what the hosted APIs charge per 1,000 pages, and when you need OCR or a layout model. Then it shows our own tool with a real run.
Disclosure: Quarrywise, the publisher of this site, makes and sells the document converter described near the end. The free tools need nothing from us.
The short answer
- A few hundred born-digital PDFs, and you already run Python: a free self-hosted library is likely enough. See the three below.
- Scans, photos of pages, forms or complex layouts: you need OCR or a layout model. Use Docling with OCR on your own machine, or a hosted parser such as LlamaParse or Unstructured. Our tool does not do OCR.
- Born-digital PDFs and Office files at volume, from an API, n8n or Make, with page citations: a hosted per-page call saves you running a service. Prices per 1,000 pages range from under one US dollar to more than fifty, depending on the tool and mode (table below).
What a RAG pipeline needs from a PDF parser
A PDF stores characters at positions on a page. It does not store "this is a heading" or "this
table continues on the next page". Plain text extraction gives you the characters in reading order
and little else. Here is the table from pages 1 and 2 of our sample
quarterly-report.pdf, as extracted by pdfplumber
0.11.10's extract_text():
Variety Hectares Tonnes Change
Elstar apple 42 1,260 +8%
Jonagold apple 35 1,015 +3%
Braeburn apple 28 812 -2%
Conference pear 19 494 +11%
Page 1 of 3
Example Orchard Cooperative | Quarterly report Q3 (sample)
Variety Hectares Tonnes Change
Comice pear 11 253 +5%
Kordia cherry 6 54 -9%
Opal plum 4 48 +1%
Total 145 3,936 +5%
The page footer and the running header sit in the middle of the table, the header row appears
twice, and nothing marks the columns. In the same file, the bullet points came out as
(cid:127). Embed that, and a question about pear harvests may retrieve a chunk that
starts with "Page 1 of 3".
What a parser for RAG should give you:
- Structure: real heading levels, lists, and tables as tables, so you can chunk by section and an LLM can read a table row by row.
- Page numbers on every chunk: so an answer can cite "page 2" and a reader can check it.
- Running headers, footers and page numbers removed: they repeat on every page and add noise to every chunk.
- Honest failures: a scanned page has no text layer. A parser without OCR should say so, not return an empty string that quietly leaves the document out of your index.
- The same output for the same file: so chunk IDs stay stable and you can update a vector store without duplicates.
Self-hosted and free: MarkItDown, pymupdf4llm, Docling
All three are Python libraries you install and run yourself. The software is free; you pay for the machine and the time to keep it working. We describe them from their own documentation and did not benchmark them for this guide.
- MarkItDown (Microsoft,
MIT licence, version 0.1.9 on PyPI) converts PDF, Word, PowerPoint, Excel, HTML and more to
Markdown. Install PDF support with
pip install 'markitdown[pdf]'. Its README says it is meant for text analysis and "may not be the best option for high-fidelity document conversions". OCR for PDFs comes from a separate plugin. - pymupdf4llm (version 2.0 on PyPI) turns PDFs into Markdown, JSON or text for LLM and RAG use. It builds on PyMuPDF, which PyPI lists as dual licensed: GNU AGPL 3.0 or a commercial licence from Artifex. Check that before you ship it inside a closed product or a hosted service.
- Docling (MIT licence, version 2.144.0 on PyPI) reads PDF, Office files, HTML and more, with page layout, reading order and table structure, OCR for scanned PDFs, and local execution for sensitive data. It ships its own chunkers (HierarchicalChunker, HybridChunker) that attach headings to each chunk.
When free is enough: you have a few hundred files, you already run Python somewhere, and you can check the output yourself. It stops being enough when you need it behind an API for other people or tools, when no one wants to keep a parsing service and its dependencies running, or when a workflow tool such as n8n or Make must call it.
Hosted APIs compared: price per 1,000 pages
Prices below were read from each vendor's own page on 28 October 2026, and the dollar figures per 1,000 pages are our arithmetic from them. These tools do different amounts of work per page, so the cheapest line is not the best for every file. Check the vendor page before you budget: prices change.
| Option | What you get | Price per 1,000 pages | Free allowance |
|---|---|---|---|
| MarkItDown, pymupdf4llm, Docling (self-hosted) | Markdown; Docling also OCR, layout and chunkers | USD 0 plus your own compute | Unlimited |
| LlamaParse, Fast tier | Spatial text only, without Markdown | USD 1.25 (1 credit per page) | 10,000 credits a month on the free plan (about 3,333 Cost-effective pages) |
| LlamaParse, Cost-effective tier | Markdown | USD 3.75 (3 credits per page) | |
| LlamaParse, Agentic tier | Layout-aware parsing with LLMs or vision models | USD 12.50 (10 credits per page) | |
| LlamaParse, Agentic Plus tier | The same, for the most complex documents | USD 56.25 (45 credits per page) | |
| Unstructured, pay as you go | Partitioning with Fast, High Res and VLM strategies, chunking, enrichment | USD 15.00 (USD 0.015 per page) | The first 10,000 pages |
| Quarrywise PDF to Markdown & RAG Chunks (on Apify) | Markdown, JSON blocks or page-cited chunks; no OCR (scans flagged, free) | USD 0.40 for the pages, plus USD 0.003 per document: USD 0.70 as 100 PDFs of 10 pages | None from us; Apify's free plan includes USD 5 of usage a month. Failed files and scans are free |
How the LlamaParse lines were worked out: the LlamaParse pricing docs list "$1.25 per 1,000 credits" and the credits per page per tier (v2 tiers). Their docs add surcharges, for example layout extraction at 3 more credits per page, so a page can cost more than the base rate. The LlamaIndex pricing page lists paid plans from USD 50 a month with credits included.
Unstructured's pricing page gives one rate, "$0.015 a page after your first 10,000 free pages", and does not list separate prices per partitioning strategy.
We left out tools whose own pricing page did not show a per-page price when we checked.
When you need OCR or a layout model (and when you do not)
You need OCR when a page has no text layer: scans, photos, faxes, and some PDFs "printed" from images. You need a layout or vision model when the page has text but its structure is hard to recover: tables with merged cells and no lines, forms, more than two columns of text, text inside charts.
You do not need either for born-digital files: reports, manuals, policies, contracts and papers exported from Word, Google Docs or LaTeX, and slide decks. For those, the text layer is complete, and a rule-based parser that reads positions, fonts and ruling lines usually gets headings and simple tables right without a model, at a fraction of the price. A model can also misread or rewrite text. A rule-based parser cannot invent text: its errors are in reading order and structure, and unreadable fonts show up as garbled characters that a quality check can flag.
A quick test: open the PDF and try to select a line of text. If you can, the page has a text layer. If the whole page selects as one image, it needs OCR.
Many collections are mixed. Then the useful behaviour is to convert the born-digital pages cheaply and get a clear list of the pages that need OCR, so only those go to an OCR service.
Chunk size and overlap with page citations (worked example)
Two settings decide what goes into each embedding:
- Chunk size: the target length of each chunk in tokens. Smaller chunks match narrow questions more closely; larger ones keep more context together.
- Overlap: text repeated from the end of one chunk at the start of the next, so a sentence that answers a question is not cut in half at a boundary.
Here is what our tool returned for quarterly-report.pdf (3 pages) with the default chunk size of 500 tokens and an overlap of 50, in a run on 28 October 2026:
| Chunk | Heading path | Pages | Tokens (estimate) |
|---|---|---|---|
| 0 | Example Orchard Cooperative: Quarterly Report | 1 | 190 |
| 1 | Example Orchard Cooperative: Quarterly Report › 2 Harvest by variety | 1 to 2 | 179 |
| 2 | Example Orchard Cooperative: Quarterly Report | 2 to 3 | 218 |
| 3 | Example Orchard Cooperative: Quarterly Report › 4 Outlook › 4.1 Risks | 3 | 63 |
What to notice:
- Chunks follow sections, not a fixed length. The whole report is about 600 tokens of Markdown, but it became four chunks: a chunk is closed at a new heading once it holds at least 30 percent of the chunk size (150 tokens at 500), and smaller sections are packed together. Chunk 2 holds sections 3 and 4; when a chunk packs several sections, its heading path stops at the heading they share.
- The table that runs over the page break is one table. Chunk 1 cites pages 1 to 2, the header row appears once, and the footer and running header are gone (compare the raw text above).
- Overlap only applies inside one section. It repeats whole sentences from the end of the previous chunk, up to the overlap you set (at most half the chunk size). No section in this short report was split in the middle of its text, so no overlap appears here.
- Tokens are estimated at about 4 characters per token, a rule of thumb for English, not a count from your embedding model's tokenizer. Leave some margin under your model's input limit.
A rough estimate for 1,000 pages: at about 500 tokens of text per page (some 2,000 characters, a moderately dense page), 1,000 pages hold about 500,000 tokens. With chunk size 500 and overlap 50, each chunk of a long section carries up to 450 new tokens, so expect roughly 500,000 / 450 ≈ 1,100 chunks, and more when sections are short. In our tool the chunk rows are free: you pay for pages converted, whatever chunk size you choose. Your embedding provider charges for the tokens you embed, including the heading path at the top of each chunk and the overlap.
Our tool: PDF to Markdown & RAG Chunks on Apify
Our PDF to Markdown & RAG Chunks: Word, Excel, PowerPoint on the Apify Store converts PDF, Word, PowerPoint, Excel, HTML, Markdown, text and CSV files into Markdown, JSON blocks or RAG chunks. It runs its own layout code on pdfplumber, with no GPU and no language model, so the same file gives the same output every time.
- Chunks with citations: each chunk has
page_start,page_end,heading_path, a token estimate and a stablechunk_id(document ID plus chunk number), handy for upserts. - Scans flagged, never silently empty: pages without a text layer are listed in
pages_needing_ocrand not charged. - A quality report per page: characters, words, a garble score for unreadable fonts, tables, images and the removed header and footer lines, with their text.
- Tables kept: ruled tables, simple aligned tables without lines, tables joined across pages; tables with merged cells are written as HTML so no cell lands in the wrong column.
- JSON for other pipelines: JSON blocks mode gives headings, paragraphs, list items and tables, each with its page and heading path (a "PDF to JSON" output).
A real run
The input below is the one behind the chunk table above. It is the call we ran on 28 October 2026, through the Apify API with our own token:
{
"documentUrls": ["https://quarrywise.com/samples/quarterly-report.pdf"],
"outputFormat": "chunks",
"chunkSize": 500,
"chunkOverlap": 50
}
One of the four chunk rows it returned (some fields left out, the table shortened):
{
"row_type": "chunk",
"source": "https://quarrywise.com/samples/quarterly-report.pdf",
"file_name": "quarterly-report.pdf",
"document_id": "9abfce51c8d261d9c32c",
"status": "ok",
"format": "pdf",
"title": "Example Orchard Cooperative: Quarterly Report",
"chunk_id": "9abfce51c8d261d9c32c-0001",
"chunk_index": 1,
"chunk_count": 4,
"heading_path": ["Example Orchard Cooperative: Quarterly Report", "2 Harvest by variety"],
"page_start": 1,
"page_end": 2,
"token_estimate": 179,
"char_count": 713,
"text": "# Example Orchard Cooperative: Quarterly Report\n\n## 2 Harvest by variety\n\nThe table lists the harvested area and the delivered weight per variety, ...\n\n| Variety | Hectares | Tonnes | Change |\n|---|---|---|---|\n| Elstar apple | 42 | 1,260 | +8% |\n...\n| Total | 145 | 3,936 | +5% |\n\n### 2.1 Notes on the table\n\n..."
}
The run took about 6 seconds from start to finish, 2 of them converting. At the listed prices it costs USD 0.003 for the document plus 3 pages × USD 0.0004 = USD 0.0042, plus Apify's start event.
A second run in Markdown mode, with four of our sample PDFs, shows the failure cases:
quarterly-report.pdf: statusok, 3 billable pages. The quality report lists the 4 removed texts: the running header and "Page 1 of 3" to "Page 3 of 3".price-list.pdf: statusok, 1 billable page, two tables, one of them without ruling lines.scanned-notice.pdf: statusneeds_ocr, 0 billable pages, with the message "No text layer found: this looks like a scanned or image-only document. It needs OCR, which this Actor does not do. Nothing was charged."two-column-article.pdf: our own sample link did not answer in time on that run and came back as a free error row (download_failed, reasontimeout). The run carried on with the next file. Sending that link again in a new run is the fix.
Cost at the listed prices: 2 documents × USD 0.003 + 4 pages × USD 0.0004 = USD 0.0076. The scan and the failed download cost nothing.
Price and worked examples
Price when this guide was written: pay per event, USD 0.003 per document converted and USD 0.0004 per page converted, plus Apify's standard Actor start event, a fraction of a cent per run. A page is a PDF page or slide with text; for Word, Excel, HTML, Markdown, text and CSV files, every 3,000 characters of output count as one page. The Store page always shows the current price.
| Batch | Math | Cost |
|---|---|---|
| 1,000 pages as 100 PDFs of 10 pages | 100 × 0.003 + 1,000 × 0.0004 = 0.30 + 0.40 | USD 0.70 |
1,000 pages as one 1,000-page PDF (set maxPages to 1000) | 1 × 0.003 + 1,000 × 0.0004 | USD 0.403 |
1,000 pages as 1,000 one-page PDFs (set maxDocuments to 1000) | 1,000 × 0.003 + 1,000 × 0.0004 = 3.00 + 0.40 | USD 3.40 |
1,000 PDFs of 10 pages, 10,000 pages (set maxDocuments to 1000) | 1,000 × 0.003 + 10,000 × 0.0004 = 3.00 + 4.00 | USD 7.00 |
| One 100-page PDF | 0.003 + 100 × 0.0004 | USD 0.043 |
| A 40-slide deck | 0.003 + 40 × 0.0004 | USD 0.019 |
| A Word file with 12,000 characters of output | 0.003 + 4 × 0.0004 | USD 0.0046 |
| A 30-page scan (needs OCR) | nothing converted | USD 0 |
For a very long PDF, raise maxPages and run with 2 to 4 GB of memory; each document
gets up to 10 minutes. A document that runs past 10 minutes or the memory gets a free
too-complex error row, and a file over 50 MB needs a higher maxFileSizeMb
(at most 100). The examples leave out the start event. Free: documents that fail, pages that need
OCR, empty pages and documents, and notice and error rows. Set a maximum charge per run in
the run options: the tool converts as many pages as that budget allows, says so in the document's
warnings, and stops cleanly.
Against the hosted APIs in the table above, the same 100 PDFs of 10 pages would cost USD 3.75 on LlamaParse's Cost-effective tier and USD 15.00 on Unstructured's pay-as-you-go rate (after their free allowances). Those services also do OCR and model-based layout; ours does not. Pay for those only on the pages that need them.
Calling it from Python, n8n and Make
The API: the first call starts the run and waits up to 60 seconds (the API
maximum) for it to finish; maxTotalChargeUsd caps what the run may charge. If the run
is still going after that, the second call waits again, as often as needed. The last one reads the
results.
curl -s -X POST "https://api.apify.com/v2/acts/quarrywise~pdf-to-markdown-rag/runs?waitForFinish=60&maxTotalChargeUsd=0.10" \
-H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
--data '{"documentUrls":["https://quarrywise.com/samples/quarterly-report.pdf"],"outputFormat":"chunks","chunkSize":500,"chunkOverlap":50}'
# if data.status is still RUNNING, wait again (up to 60 s per call) with data.id from the answer:
curl -s "https://api.apify.com/v2/actor-runs/RUN_ID?waitForFinish=60" -H "Authorization: Bearer $APIFY_TOKEN"
# then, with data.defaultDatasetId from the answer:
curl -s "https://api.apify.com/v2/datasets/DATASET_ID/items?clean=1" -H "Authorization: Bearer $APIFY_TOKEN"
For one call that waits and returns the rows, the Actor's README documents
POST https://api.apify.com/v2/acts/quarrywise~pdf-to-markdown-rag/run-sync-get-dataset-items
with the same body. It waits up to 300 seconds and answers HTTP 408 after that, so start large
documents with the calls above instead.
Python (standard library only), the same calls, waiting until the run has finished and keeping only chunk rows:
import json, os, urllib.request
TOKEN = os.environ["APIFY_TOKEN"]
API = "https://api.apify.com/v2"
def call(url, body=None):
request = urllib.request.Request(
url, data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"})
with urllib.request.urlopen(request, timeout=90) as response:
return json.load(response)
run = call(f"{API}/acts/quarrywise~pdf-to-markdown-rag/runs?waitForFinish=60&maxTotalChargeUsd=0.10", {
"documentUrls": ["https://quarrywise.com/samples/quarterly-report.pdf"],
"outputFormat": "chunks", "chunkSize": 500, "chunkOverlap": 50,
})["data"]
while run["status"] in ("READY", "RUNNING"): # waitForFinish is at most 60 seconds per call
run = call(f"{API}/actor-runs/{run['id']}?waitForFinish=60")["data"]
if run["status"] != "SUCCEEDED":
raise SystemExit(f"Run {run['id']} ended as {run['status']}: check the run in Apify Console")
rows = call(f"{API}/datasets/{run['defaultDatasetId']}/items?clean=1")
chunks = [r for r in rows if r["row_type"] == "chunk"] # notice and error rows have no text
for c in chunks:
print(c["chunk_id"], c["page_start"], c["page_end"], " > ".join(c["heading_path"]))
Keep rows whose row_type is chunk. The same dataset holds a
notice row for each document with warnings and an error row for each
input that failed.
n8n: add Apify's verified n8n node (@apify/n8n-nodes-apify; on n8n
Cloud the instance owner first enables verified community nodes). Use the operation "Run an Actor
and Get Dataset" and select the Actor by ID, lnxVsBgOB9lHLTIYG, or By URL with exactly
https://console.apify.com/actors/lnxVsBgOB9lHLTIYG/input (the Store page URL does not
work in that field). Send documentUrls with outputFormat
chunks, keep the chunk rows and pass them to your vector store node.
Make: Apify's app, the "Run an Actor" module, then "Get Dataset Items".
Zapier: "Run Actor", then "Fetch dataset items". AI agents: add
the Actor as a tool through Apify's MCP server at
https://mcp.apify.com?tools=quarrywise/pdf-to-markdown-rag (needs an Apify sign-in or
API token).
The limits, honestly
- No OCR. Scanned and image-only pages are detected, listed and free, not converted.
- Tables without ruling lines are recognised only when their columns line up cleanly. Merged cells without lines, tables inside text columns and more than two columns of text may come out as paragraphs. The quality report helps you find those pages.
- Charts and images are not described; only their count is in the quality report.
- Password-protected PDFs, legacy .doc and .ppt, OpenDocument, EPUB and RTF are not read; each gets a free error row.
- Files up to 50 MB by default (at most 100 MB), up to 1,000 documents per run (
maxDocuments, default 100), and up to 300 PDF pages or slides per document by default (maxPages, at most 5,000). - Links behind a login are not supported. Use a public or signed link, or upload the file.
Ready-made inputs on the Store: PDF to RAG chunks with page citations, extract tables from PDF to JSON and Markdown and HTML to Markdown for web pages.
See the PDF to Markdown tool on the Apify Store
FAQ
Is there a free PDF to Markdown API?
Not without limits. LlamaParse's free plan includes 10,000 credits a month and Unstructured gives the first 10,000 pages free. Our tool has no free allowance of its own, but Apify's free plan includes USD 5 of usage a month, about 7,000 pages as 10-page PDFs. For unlimited free use, run MarkItDown, pymupdf4llm or Docling yourself, behind your own small API if you need one.
What is a cheaper LlamaParse alternative for born-digital PDFs?
If your PDFs have a text layer, you do not need a model-based tier. A self-hosted parser such as Docling (its layout models run on your own machine) or a rule-based one such as ours (hosted, no models, USD 0.40 per 1,000 pages plus USD 0.003 per document) costs less. Keep LlamaParse or another OCR service for the scanned pages.
Which chunk size should I use?
Start at 500 tokens with 50 tokens of overlap, test it with real questions, and only then tune. Go smaller if answers are short facts, larger if answers need several paragraphs of context.
Does the same call convert HTML, Word and Excel?
Yes. HTML pages keep the main content and drop navigation, header, footer and scripts. Word, PowerPoint and Excel files keep headings, lists and tables. They are billed per 3,000 characters of output (slides per slide).
Do you keep my files?
No. Documents are processed in memory during the run and stored only in the run's own dataset and key-value store, which you control and can delete. Nothing is used for training.
Quarrywise is independent and isn't affiliated with or endorsed by LlamaIndex, Unstructured, Microsoft, Artifex, the Docling project or Apify.
Sources
- LlamaParse docs: Pricing (credits per page per tier, USD per 1,000 credits)
- LlamaParse docs: Tiers (what each tier does)
- LlamaIndex: Pricing (plans and free credits)
- Unstructured: Pricing
- MarkItDown on GitHub
- PyMuPDF4LLM documentation and PyMuPDF on PyPI (licence)
- Docling on GitHub
- Docling: Chunking
- PDF to Markdown & RAG Chunks on the Apify Store (README, prices, limits)