PDF to Text & Markdown Extractor for RAG
Pricing
from $3.00 / 1,000 pdf processeds
PDF to Text & Markdown Extractor for RAG
Convert PDFs from URLs or a dataset to clean text or Markdown with tables, metadata and RAG-ready chunks (page ranges + token estimates). Optional OCR for scanned pages. $0.003 per PDF; failed or password-protected PDFs are free.
Pricing
from $3.00 / 1,000 pdf processeds
Rating
0.0
(0)
Developer
Majd Hijazin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
PDF to Text & Markdown Extractor — tables and RAG chunks, bulk URLs
Convert PDF to text or PDF to Markdown in bulk. Paste PDF URLs (or point the Actor at a dataset of URLs) and get one clean JSON item per PDF: full Markdown or plain text, tables as rows and Markdown, metadata (title, author, dates, page count) and RAG-ready chunks with page ranges and token estimates. Scanned pages are detected and can be read with built-in OCR.
$0.003 per processed PDF ($3 per 1,000). Password-protected, scanned-without-OCR, broken, oversized or unreachable PDFs are free. Platform usage is included.
Use it as a PDF parser API for LLM agents, retrieval-augmented generation (RAG), search indexing, document analytics, or anywhere you need to extract text from PDF files at scale.
Why this PDF extractor
- Markdown that LLMs read well — headings (
#,##,###) from font sizes and numbered sections, bullet and numbered lists, paragraphs reflowed across line breaks, soft hyphens removed ("hyphen-ation" → "hyphenation") while real compounds stay ("task-specific"). - Tables — detected from column alignment and returned as
rows[][]and a Markdown table. Turn off withextractTables: false. - RAG chunks built in —
chunks[]follow heading, paragraph and sentence boundaries, withpageStart/pageEnd,tokensEstimate,sectionand a stableid. ConfigurechunkSizeandchunkOverlap. - Clean text for retrieval — running headers, footers and page numbers are removed; two-column papers keep their reading order; out-of-order link text is re-inserted where it belongs.
- Bulk input — up to 10,000 URLs per run, or read URLs from any Apify dataset (
datasetId+datasetUrlField, dot paths and arrays supported). Duplicates removed. - Friendly links — arXiv
/abs/pages, Google Drive share links, Dropbox?dl=0and GitHubbloblinks are converted to direct downloads. - Clear statuses, honest billing — every PDF gets a
statusandstatusMessage; you only pay forsuccessandpartial. - Permissive open-source stack — Mozilla PDF.js (Apache-2.0) and Tesseract (Apache-2.0). No AGPL/GPL components. See LICENSES.md.
Quick start
- Click Try for free. The input is prefilled with the "Attention Is All You Need" paper on arXiv.
- Add your own PDF URLs (one per line) or a dataset ID.
- Choose Markdown or Plain text, then Start.
- Open the Overview, Content, RAG chunks, Tables or Not charged / failures tabs, or download JSON, CSV, Excel.
Example input
{"pdfUrls": ["https://arxiv.org/pdf/1706.03762","https://www.irs.gov/pub/irs-pdf/fw9.pdf"],"outputFormat": "markdown","extractTables": true,"chunkSize": 800,"chunkOverlap": 100,"maxPages": 200}
Read URLs from another Actor's dataset
{"datasetId": "YOUR_DATASET_ID","datasetUrlField": "pdfUrl","outputFormat": "text","chunkSize": 500}
Example output (one item per PDF, shortened)
{"url": "https://arxiv.org/pdf/1706.03762","status": "success","statusMessage": "All pages processed.","title": "Attention Is All You Need","author": null,"creationDate": "2024-04-10T21:11:43.000Z","pageCount": 15,"pagesProcessed": 15,"truncated": false,"pagesNeedingOcr": [],"outputFormat": "markdown","markdown": "# Attention Is All You Need\n\nAshish Vaswani ... \n\n## Abstract\n\nThe dominant sequence transduction models ...","wordCount": 6300,"tokensEstimate": 10200,"tableCount": 8,"tables": [{"index": 0,"page": 6,"rowCount": 4,"columnCount": 4,"rows": [["Self-Attention", "O(n2 · d)", "O(1)", "O(1)"], ["Recurrent", "O(n · d2)", "O(n)", "O(n)"]],"markdown": "| Self-Attention | O(n2 · d) | O(1) | O(1) |\n| --- | --- | --- | --- |\n| Recurrent | O(n · d2) | O(n) | O(n) |"}],"chunkCount": 17,"chunks": [{"id": "3f2a9c1b7e-0","index": 0,"text": "# Attention Is All You Need\n\n...","pageStart": 1,"pageEnd": 2,"tokensEstimate": 790,"charCount": 3160,"section": "Attention Is All You Need"}],"charged": true,"chargedEvents": { "pdf-processed": 1 },"processingMs": 1468}
Numbers above come from a local test run; your values may differ slightly between Actor versions. The full field list is in the dataset schema (.actor/dataset_schema.json).
Pricing
Pay per event — you pay only for results:
| Event | Price | When it is charged |
|---|---|---|
pdf-processed | $0.003 per PDF | Once per PDF with status success or partial (text was extracted). Any page count up to maxPages. |
ocr-page | $0.002 per page | Only with ocr: true, per scanned page that OCR actually read, and only when the PDF itself is processed. |
Free (never charged): password_protected, needs_ocr, empty, not_pdf, too_large, download_failed, timeout, parse_error, invalid_url, blocked_url, skipped_budget.
Examples: 1,000 digital PDFs = $3.00. 100 PDFs where 10 are 5-page scans with OCR on = 100 × $0.003 + 50 × $0.002 = $0.40.
Spending limit: set Maximum cost per run (maxTotalChargeUsd). The Actor reserves budget before each PDF and OCR page, never exceeds the limit, and marks the remaining URLs skipped_budget (free). If the limit is below one PDF ($0.003) the run stops immediately with BUDGET_TOO_LOW.
Statuses
| Status | Charged | Meaning |
|---|---|---|
success | yes | All requested pages read (truncated: true if the PDF is longer than maxPages). |
partial | yes | Text returned, but some pages are scanned (see pagesNeedingOcr), failed (pageErrors) or the per-PDF time limit was reached. |
needs_ocr | no | No text layer — the PDF is a scan. Re-run with ocr: true. |
password_protected | no | Encrypted with a user password; skipped. (Owner-password "restricted" PDFs that open normally are extracted.) |
empty | no | No text and no images. |
not_pdf | no | The URL returned HTML or another file type. Use the direct PDF link. |
too_large | no | Bigger than maxFileSizeMb (default 50 MB, max 200 MB). |
download_failed / timeout | no | HTTP error, DNS failure, or time limit reached before any text. Transient errors (429/5xx/network) are retried twice. |
parse_error | no | Corrupted or unsupported PDF. |
invalid_url / blocked_url | no | Not an http(s) URL, or a private/internal address. |
skipped_budget | no | Your maximum cost per run was reached. |
Use it from code, agents and RAG pipelines
API (cURL)
curl -X POST "https://api.apify.com/v2/acts/impressionable_lupine~pdf-text-extractor/run-sync-get-dataset-items?maxTotalChargeUsd=0.10" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"pdfUrls":["https://arxiv.org/pdf/1706.03762"],"outputFormat":"markdown","chunkSize":800}'
JavaScript / TypeScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('impressionable_lupine/pdf-text-extractor').call({ pdfUrls: ['https://arxiv.org/pdf/1706.03762'], outputFormat: 'markdown', chunkSize: 800, chunkOverlap: 100 },{ maxTotalChargeUsd: 0.10 },);const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const pdf of items) console.log(pdf.status, pdf.title, pdf.chunkCount);
Python — load chunks into a vector store
from apify_client import ApifyClientclient = ApifyClient("APIFY_TOKEN")run = client.actor("impressionable_lupine/pdf-text-extractor").call(run_input={"pdfUrls": ["https://arxiv.org/pdf/1706.03762"], "chunkSize": 500, "chunkOverlap": 50},max_total_charge_usd=0.10,)docs = []for pdf in client.dataset(run["defaultDatasetId"]).iterate_items():if pdf["status"] not in ("success", "partial"):continuefor c in pdf["chunks"]:docs.append({"id": c["id"],"text": c["text"],"metadata": {"source": pdf["url"], "title": pdf["title"], "pages": f'{c["pageStart"]}-{c["pageEnd"]}', "section": c["section"]},})# docs -> embed and upsert into Pinecone, Qdrant, pgvector, Chroma, Weaviate ...
The RAG chunks dataset view (?view=chunks) returns one row per chunk, ready for embedding pipelines and no-code tools (Make, n8n, Zapier).
MCP — let AI agents read PDFs
Add the Actor to the Apify MCP server so Claude, Cursor, ChatGPT or any MCP client can call it as a tool:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=impressionable_lupine/pdf-text-extractor","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Then ask your agent: "Read https://arxiv.org/pdf/1706.03762 and summarize section 3 with page references." The agent receives Markdown plus chunks with page numbers, so it can cite pages. Tip for agents: set chunkSize: 0 when you only need the full Markdown, and pass maxTotalChargeUsd on every call.
Input options
| Field | Default | Description |
|---|---|---|
pdfUrls | — | List of PDF URLs. |
datasetId / datasetUrlField | — / url | Read URLs from an Apify dataset (dot paths, arrays). |
outputFormat | markdown | markdown or text. |
extractTables | true | Detect tables (rows + Markdown). |
chunkSize / chunkOverlap | 800 / 100 | RAG chunk size and overlap in estimated tokens. chunkSize: 0 disables chunks. |
removeHeadersFooters | true | Drop running headers, footers and page numbers. |
includePages | false | Add per-page text in pages[]. |
ocr / maxOcrPagesPerPdf | false / 20 | OCR scanned pages (English), with a per-PDF page cap. |
maxPages | 200 | Pages per PDF (0 = all, hard cap 2,000). |
maxFileSizeMb | 50 | Max download size (1–200 MB). |
timeoutSecs | 180 | Per-PDF time budget (download + extraction). |
maxConcurrency | 3 | PDFs in parallel (fits in the default 1 GB). |
Limitations
- Tables are best effort. Detection uses column alignment, which works well on digitally generated reports, papers and invoices. Merged cells, tables drawn as images, rotated tables and very dense borderless layouts can be split or merged; the text is still in
markdown/text. - OCR is English-only in this version and slower (about 2–5 s per page). Handwriting and low-quality scans give lower
confidence. Withoutocr: true, scanned pages are listed inpagesNeedingOcr. - Layout: reading order follows the PDF's content stream, which is correct for most papers and reports (including two-column). Unusual producers (some design tools) may interleave text boxes.
- Not extracted: images/figures, charts, form-field values typed into fillable forms, annotations, attachments and math as LaTeX.
- Password-protected PDFs are skipped — there is no password input by design.
- Only public http(s) URLs; private network addresses are blocked. Token counts are estimates (~4 characters per token).
FAQ
How do I convert a PDF to Markdown? Add the URL to pdfUrls, keep outputFormat: markdown, run. The markdown field contains headings, lists and tables.
How do I extract text from a PDF for RAG? Use the chunks array (or the RAG chunks view). Each chunk has text, page range, token estimate and section heading — embed it directly.
Can it read scanned PDFs? Yes, with ocr: true (+$0.002 per OCR page). Without it, scans return needs_ocr and are free.
What happens with password-protected PDFs? They return password_protected and are not charged.
Is there a page limit? maxPages (default 200, up to 2,000). The price per PDF does not depend on page count.
Can I process thousands of PDFs? Yes — up to 10,000 URLs per run, or feed a dataset from a crawler. For very large batches, raise memory and maxConcurrency together.
Do failed PDFs cost anything? No. Only success and partial items are charged; every item shows charged and chargedEvents.
Which libraries does it use? Mozilla PDF.js and Tesseract (both Apache-2.0). No AGPL software. Full list: LICENSES.md.
Is my data stored? PDFs are processed in memory during the run. Results stay in your run's dataset under your Apify data-retention settings.
Support
Found a PDF that extracts poorly? Open an issue on the Actor page with the URL and what you expected — layout fixes ship regularly.