PDF Text Extractor & Markdown for LLM RAG: OCR, Tables, Chunks
Pricing
Pay per event
PDF Text Extractor & Markdown for LLM RAG: OCR, Tables, Chunks
Convert PDF to clean Markdown for LLM & RAG pipelines: text extractor with OCR fallback (50 languages), GitHub-flavored table reconstruction (incl. tables spanning pages), right-to-left script support, and optional RAG chunking. ~$0.005 for a 20-page text PDF. Pay only for pages with real text.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Pankovs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Turn any PDF into clean Markdown for LLMs and RAG: headings, lists and GitHub-flavored tables rebuilt from the page, OCR for scanned pages in 50 languages, and ready-to-embed chunks with page and section citations. About $0.008 for a 20-page text PDF.
Handles what most PDF to Markdown Actors don't: tables that span pages, right-to-left scripts (Arabic/Hebrew) in correct reading order, filled form fields, and RAG chunks that never cut a table in half.
Use cases
- RAG and chatbots: feed contracts, manuals or research papers into a vector store, with
chunksMetapage ranges and section paths so answers can cite "page 4, section 3.2". - LLM data pipelines: turn reports, filings and invoices into Markdown an LLM reads reliably, tables included.
- Scanned archives: OCR old scans and image-only PDFs in 50 languages; pages with a real text layer skip OCR and cost less.
- Multilingual documents: Arabic, Hebrew, Persian and Urdu text comes out in reading order; Cyrillic, Greek, CJK and Indic scripts are supported.
How to use
- Paste one or more PDF links into
pdfs(or upload a file aspdfFile). - Pick options if you need them:
ocrLanguagesfor scans,outputChunksfor RAG chunks,includePageMarkers. - Run the Actor and download the dataset (JSON, CSV, Excel), or call it from the API, Make, Zapier or LangChain.
Input
| Field | Type | Default | Description |
|---|---|---|---|
pdfs | array of strings | [] | http(s) URLs of PDF files to convert. |
pdfFile | string | none | Optional single PDF uploaded via the Apify Console. |
ocrMode | auto | always | never | auto | auto OCRs only low-text pages; always OCRs every page; never uses the text layer only. |
ocrLanguages | string | eng | OCR languages for scanned pages, Tesseract codes joined with + (e.g. eng+rus), up to 5. See below. |
maxPagesPerPdf | integer | 200 | Stop processing a PDF after this many pages. |
extractTables | boolean | true | Rebuild tables as GitHub-flavored Markdown tables. See below. |
outputChunks | boolean | false | Also split the Markdown into RAG chunks. |
chunkSize | integer | 2000 | Target chunk length in characters (chunks end on a boundary within 15%). |
chunkOverlap | integer | 200 | Overlap between consecutive chunks, at most half of chunkSize (a larger value is reduced to chunkSize / 2, with a warning). |
includePageMarkers | boolean | false | Insert <!-- page: N --> markers before each page's Markdown. |
At least one of pdfs or pdfFile must be set.
OCR languages
50 languages, all built into the actor (no downloads at run time). Combine up to 5 codes with + (eng+deu).
Any other code, or more than 5 codes, is rejected as an input error (run fails up front, nothing charged).
OCR models: Tesseract tessdata_best (Apache-2.0) via @tesseract.js-data.
eng English, rus Russian, deu German, fra French, spa Spanish, por Portuguese, ita Italian, pol Polish, lav Latvian, ukr Ukrainian, nld Dutch, swe Swedish, dan Danish, nor Norwegian, fin Finnish, ces Czech, slk Slovak, hun Hungarian, ron Romanian, tur Turkish, ell Greek, ara Arabic, heb Hebrew, fas Persian, urd Urdu, hin Hindi, ben Bengali, tam Tamil, tel Telugu, mar Marathi, tha Thai, vie Vietnamese, ind Indonesian, msa Malay, chi_sim Chinese (Simplified), chi_tra Chinese (Traditional), jpn Japanese, kor Korean, bul Bulgarian, srp Serbian (Cyrillic), hrv Croatian, slv Slovenian, lit Lithuanian, est Estonian, cat Catalan, glg Galician, eus Basque, afr Afrikaans, swa Swahili, fil Filipino.
List only the languages your scans contain. Each extra language slows OCR and can hurt accuracy
(eng+rus on an English scan puts Cyrillic look-alike letters into English words; measured with
scripts/ocr-bench.mjs), so the default is eng.
Script auto-detect: if a page's OCR confidence is below 70%, the page's script is detected (Tesseract
OSD) and, if none of your ocrLanguages covers it, the page is re-read with that script's language
added: Latin eng, Cyrillic rus, Greek ell, Arabic ara, Hebrew heb, Devanagari hin, Bengali
ben, Tamil tam, Telugu tel, Thai tha, Chinese chi_sim, Japanese jpn, Korean kor (e.g. a Greek
scan with the default eng is re-read with eng+ell). The better result is kept and a warning suggests
fixing ocrLanguages. Detection needs a page's worth of text; very short pages are not re-read. If
confidence is still below 60% (e.g. a Georgian or Armenian scan with no model), the page text is kept but
flagged in warnings and the page is not charged.
Text-layer PDFs work in any language/script, including Arabic/Hebrew (right-to-left runs are re-ordered into reading order, including PDFs that map whole Arabic words to one glyph, e.g. WeasyPrint/cairo; Chrome-printed Arabic, which encodes medial yeh/heh as Persian/Urdu letters, is folded back to Arabic yeh/heh unless the page contains Persian/Urdu-only letters).
Tables
Tables are rebuilt from the positions of the text on the page (aligned columns + rows), so bordered and borderless tables both work. They are emitted as GitHub-flavored Markdown:
| Quarter | Revenue | Profit || --- | --- | --- || Q1 2025 | 1,250.00 | 269.50 |
A table that continues on the next page (with or without a repeated header row) is merged into one
table unless includePageMarkers is on. RAG chunks never cut a table in half: a table longer than
chunkSize becomes its own chunk. When the layout is ambiguous (multi-column prose, sparse
figure labels, fewer than 3 rows), the text stays plain paragraphs instead of a broken table.
Every printed line stays its own row, even in dense tables grouped in blocks of five (tax tables).
Tables printed side by side (or next to a prose column) come out as separate tables. A cell merged
across columns goes into its first column; a group label centered over several rows (rowspan, e.g.
LLaMA next to 7B, 13B, 33B) is repeated on each of those rows.
Pages packed with more than 4,000 text cells or 600 multi-cell lines, or laid out so that table search would
have to recheck the same text hundreds of times, skip table search: their text comes out as plain paragraphs
and the item gets a warning naming the page.
Limits: multi-level headers come out as separate header rows, header text stacked over several
lines may stay above the table as plain text, and tables on scanned (OCR) pages stay plain text.
Output
One dataset item per PDF, for example (arXiv 1706.03762 with outputChunks on, shortened):
{"url": "https://arxiv.org/pdf/1706.03762v7","fileName": "...","pageCount": 15,"pagesProcessed": 15,"pagesOcr": 0,"markdown": "## Attention Is All You NeedAshish Vaswani Google Brain ...### AbstractThe dominant sequence transduction models ...","chunks": ["## Attention Is All You Need...", "..."],"chunksMeta": [{ "index": 0, "pageStart": 1, "pageEnd": 1, "headings": [] },{ "index": 3, "pageStart": 4, "pageEnd": 5, "headings": ["Attention Is All You Need", "3 Model Architecture"] }],"metadata": { "title": "...", "author": "...", "created": "..." },"warnings": [],"error": null}
-
url,fileName,pageCount(pages in the PDF),pagesProcessed(pages converted),pagesOcr(pages billed asocr-page) -
markdown: the whole document. Filled-in form fields (text and choice fields, checked checkboxes, the selected radio option) are appended to their page as- field name: valuelines. WithincludePageMarkers, pages without text keep a bare marker. -
chunks(withoutputChunks): pieces ofmarkdownof aboutchunkSizecharacters (within 15% either way). A chunk ends at the best boundary in that range: before a heading, then a paragraph break, a line break, a sentence end, and only then a space between words. Words are never cut, a heading is never left at the end of a chunk, and the overlap with the previous chunk starts at the beginning of a word (at mostchunkOverlapcharacters). Tables are never split: a table longer thanchunkSizebecomes its own chunk, together with the heading right above it. -
chunksMeta(withoutputChunks): one entry per chunk, in the same order, so you can cite pages and sections:{ "index": 3, "pageStart": 4, "pageEnd": 5, "headings": ["Attention Is All You Need", "3 Model Architecture"] }pageStart/pageEndare the PDF page numbers the chunk text comes from;headingsis the section path where the chunk's new text begins (top level first, empty before the first heading). Numbered section titles in bold at the normal text size (2.2 Architecturein a two-column paper) count as headings too, nested by their number. -
metadata:title,author,created(ISO 8601 in UTC, e.g.1999-09-21T17:45:50Z; the raw PDF value if it is not a date) -
warnings: per-page notes (low OCR confidence, OCR language switch, dense table pages) and a note whenmaxPagesPerPdfstopped before the end:Stopped at maxPagesPerPdf=6 of 28 pages: pages 7-28 were not converted or charged. -
error: set on failure (for exampleThe URL returned an empty response (0 bytes), not a PDF), or on an early stop once the run's charge limit is reached; the item still contains whatever was processed.
Pricing (pay-per-event)
| Event | Price | When |
|---|---|---|
document | $0.00416 | once per PDF that produced at least one billed page |
text-page | $0.000192 | per page extracted from the text layer |
ocr-page | $0.01 | per page whose text came from OCR |
apify-actor-start | $0.00005 | Apify's standard start event, once per GB of memory (at most twice, runs are capped at 2 GB) |
Worked examples:
- One 20-page text PDF: $0.00416 + 20 x $0.000192 = $0.008, plus $0.0001 for the run start.
- A nightly job converting 50 new 20-page reports and one 10-page scan: 50 x $0.008 + ($0.00416 + 10 x $0.01) + $0.0001 = $0.504 per night, about $15 a month.
Failed PDFs (download error, password-protected, corrupt, not a PDF) and PDFs without any extractable text are never
charged, not even the document event.
FAQ and limits
Am I charged for OCR that found nothing? No. If OCR returns no text, or less text than the page's own text layer, the
text layer is used and the page is billed as text-page (or not at all if the page has no text). If OCR confidence is
below 60% (usually a language or script missing from ocrLanguages), the page is flagged in warnings and not charged.
Empty pages are free. With ocrMode=never, a page with no text layer is free and flagged in warnings, so a scan does
not come back as a silently empty page.
What if a link is flaky? Downloads are retried twice (1 s, 3 s backoff) on HTTP 502/503/504 and connection resets.
How big can a PDF be? maxPagesPerPdf (default 200) caps the pages converted per PDF; the item says where it stopped.
Set a maximum charge per run in the Apify Console to cap the cost; the Actor stops cleanly when it is reached.
Which tables are not rebuilt? Tables on scanned (OCR) pages stay plain text. Multi-level headers come out as separate header rows, and header text stacked over several lines may stay above the table as plain text.
More
Real output, prices and changelog: PDF to Markdown for RAG, real output and cost.