PDF to Text & Markdown Extractor for RAG avatar

PDF to Text & Markdown Extractor for RAG

Pricing

from $3.00 / 1,000 pdf processeds

Go to Apify Store
PDF to Text & Markdown Extractor for RAG

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

Majd Hijazin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

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 with extractTables: false.
  • RAG chunks built in — chunks[] follow heading, paragraph and sentence boundaries, with pageStart/pageEnd, tokensEstimate, section and a stable id. Configure chunkSize and chunkOverlap.
  • 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=0 and GitHub blob links are converted to direct downloads.
  • Clear statuses, honest billing — every PDF gets a status and statusMessage; you only pay for success and partial.
  • Permissive open-source stack — Mozilla PDF.js (Apache-2.0) and Tesseract (Apache-2.0). No AGPL/GPL components. See LICENSES.md.

Quick start

  1. Click Try for free. The input is prefilled with the "Attention Is All You Need" paper on arXiv.
  2. Add your own PDF URLs (one per line) or a dataset ID.
  3. Choose Markdown or Plain text, then Start.
  4. 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:

EventPriceWhen it is charged
pdf-processed$0.003 per PDFOnce per PDF with status success or partial (text was extracted). Any page count up to maxPages.
ocr-page$0.002 per pageOnly 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

StatusChargedMeaning
successyesAll requested pages read (truncated: true if the PDF is longer than maxPages).
partialyesText returned, but some pages are scanned (see pagesNeedingOcr), failed (pageErrors) or the per-PDF time limit was reached.
needs_ocrnoNo text layer — the PDF is a scan. Re-run with ocr: true.
password_protectednoEncrypted with a user password; skipped. (Owner-password "restricted" PDFs that open normally are extracted.)
emptynoNo text and no images.
not_pdfnoThe URL returned HTML or another file type. Use the direct PDF link.
too_largenoBigger than maxFileSizeMb (default 50 MB, max 200 MB).
download_failed / timeoutnoHTTP error, DNS failure, or time limit reached before any text. Transient errors (429/5xx/network) are retried twice.
parse_errornoCorrupted or unsupported PDF.
invalid_url / blocked_urlnoNot an http(s) URL, or a private/internal address.
skipped_budgetnoYour 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 ApifyClient
client = 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"):
continue
for 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

FieldDefaultDescription
pdfUrls—List of PDF URLs.
datasetId / datasetUrlField— / urlRead URLs from an Apify dataset (dot paths, arrays).
outputFormatmarkdownmarkdown or text.
extractTablestrueDetect tables (rows + Markdown).
chunkSize / chunkOverlap800 / 100RAG chunk size and overlap in estimated tokens. chunkSize: 0 disables chunks.
removeHeadersFooterstrueDrop running headers, footers and page numbers.
includePagesfalseAdd per-page text in pages[].
ocr / maxOcrPagesPerPdffalse / 20OCR scanned pages (English), with a per-PDF page cap.
maxPages200Pages per PDF (0 = all, hard cap 2,000).
maxFileSizeMb50Max download size (1–200 MB).
timeoutSecs180Per-PDF time budget (download + extraction).
maxConcurrency3PDFs 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. Without ocr: true, scanned pages are listed in pagesNeedingOcr.
  • 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.