PDF to Markdown and Text Extractor for LLM and RAG
Pricing
from $4.00 / 1,000 pdf converteds
PDF to Markdown and Text Extractor for LLM and RAG
Turn PDFs into clean text and Markdown for LLM and RAG pipelines. Headings from font size and weight, paragraphs reflowed, columns in reading order, repeated headers removed. Optional per-page text and embedding-sized chunks with page range and heading path. Flags scanned PDFs; no OCR.
Pricing
from $4.00 / 1,000 pdf converteds
Rating
0.0
(0)
Developer
Anton Sajin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
PDF to Markdown and Text Extractor
Turn PDFs into clean text and Markdown that an LLM can read and a RAG pipeline can index.
A PDF stores letters at positions on a page, not paragraphs. Copy the text out naively and you get lines cut in mid-sentence, words split by hy-phens, two columns shuffled into each other, and a page number in the middle of every thought. This Actor puts the text back together: headings become headings, wrapped lines become paragraphs, columns are read in order, running headers and footers are removed, and the result can be cut into chunks that know which page and which section they came from.
Give it PDF links, uploaded files, or PDFs another Actor saved. Get one result per PDF.
It does not do OCR. A scanned PDF has no text to extract. The Actor tells you so in the result (isScanned: true) instead of returning an empty string, and does not charge for it.
What you get for each PDF
- Markdown with headings inferred from font size and weight, bullet and numbered lists, simple tables, and paragraphs reflowed across line breaks, columns and pages.
- Plain text with the same reading order and paragraphs, without Markdown syntax.
- Document properties as the file states them: title, author, subject, keywords, creating program, dates, language.
- Page count, and which pages have no text layer.
- An outline: every heading with its level and page.
- Optional text per page, for page-level citations.
- Optional chunks for embedding: sized in characters or approximate tokens, never crossing a heading, broken between paragraphs where possible, each with its page range and heading path.
What you can do with it
- Feed documents to an LLM. Reports, papers, manuals and contracts as Markdown, with the structure a model needs to find its way around.
- Build a RAG index. Chunks arrive with
pageStart,pageEndandheadingPath, so an answer can cite "section 3.2, page 19". - Process what a crawler found. Point the Actor at the key-value store where a crawl saved its PDFs.
- Check a batch of files. Find out which PDFs are scans, which are password-protected, and which links do not lead to a PDF at all, without paying for them.
Input example
{"pdfUrls": ["https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.100-1.pdf","https://arxiv.org/abs/2302.13971"],"pageRange": "1-30","includeChunks": true,"chunkSize": 1200,"chunkOverlap": 150}
Where the PDFs come from
| Field | Use |
|---|---|
pdfUrls | Links to PDF files. Redirects are followed, so DOI links work. A link to an arXiv abstract page or a GitHub file page is pointed at the PDF itself. |
pdfFiles | Files uploaded from your computer in the Apify Console. |
keyValueStoreId | A key-value store that holds PDFs. Every record whose key ends in .pdf is read. |
recordKeys | Read only these records of the store, whatever their keys look like. |
Options
| Field | Default | What it does |
|---|---|---|
pageRange | all pages | Pages to read, for example 1-5, 8, 12-. |
maxPagesPerDocument | no limit | Stop reading each PDF after this many pages. |
password | none | Opens PDFs that need a password. PDFs that only restrict printing or copying open without one. |
removeHeadersFooters | true | Removes running headers, footers and page numbers. |
includePages | false | Adds pages: the plain text of every page. |
includeChunks | false | Adds chunks. |
chunkSize | 1000 | Largest chunk, in characters or tokens. |
chunkOverlap | 100 | How much of a chunk is repeated at the start of the next one. |
chunkUnit | characters | characters or tokens. Tokens are estimated at four characters each. |
Output
One item per PDF. This is from a real run on the NIST AI Risk Management Framework with includePages and includeChunks on. Long strings are cut short and the arrays show a few of their entries:
{"id": "88fe07c03eb03177","url": "https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.100-1.pdf","loadedUrl": "https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.100-1.pdf","status": "ok","errorCode": null,"errorMessage": null,"fileName": "NIST.AI.100-1.pdf","fileSizeBytes": 1946127,"fileSha256": "7576edb531d9848825814ee88e28b1795d3a84b435b4b797d3670eafdc4a89f1","pageCount": 48,"pagesProcessed": 48,"isScanned": false,"pagesWithoutText": [],"isEncrypted": false,"metadata": {"title": "Artificial Intelligence Risk Management Framework (AI RMF 1.0)","author": "National Institute of Standards and Technology","subject": "As directed by the National Artificial Intelligence Initiative Act of 2020 (P.L. 116-283), the goal of the AI RMF is to offer a resource to the organizations designing, developing, deploying, or using AI systems…","keywords": "Artificial Intelligence (AI); AI; AI RMF; AI RMF 1.0; AI systems; trustworthy and responsible AI.","creator": "LaTeX with hyperref","producer": "pdfTeX-1.40.24","creationDate": "2023-01-24T19:45:46.000Z","modificationDate": "2025-06-04T17:01:45.000Z","language": "en","pdfVersion": "1.7"},"text": "NIST AI 100-1\n\nArtificial Intelligence Risk Management Framework (AI RMF 1.0)\n\n…","markdown": "#### NIST AI 100-1\n\n# Artificial Intelligence Risk Management Framework (AI RMF 1.0)\n\n…","charCount": 104748,"wordCount": 15302,"tokenEstimate": 26187,"headings": [{ "level": 3, "text": "1. Framing Risk", "page": 9 },{ "level": 4, "text": "1.1 Understanding and Addressing Risks, Impacts, and Harms", "page": 9 },{ "level": 4, "text": "1.2 Challenges for AI Risk Management", "page": 10 },{ "level": 5, "text": "1.2.1 Risk Measurement", "page": 10 }],"pages": [{ "page": 1, "text": "NIST AI 100-1\n\nArtificial Intelligence Risk Management Framework (AI RMF 1.0)", "charCount": 77 }],"chunks": [{"index": 50,"text": "#### 3.2 Safe\n\nAI systems should “not under defined conditions, lead to a state in which human life, health, property, or the environment is endangered” (Source: ISO/IEC TS 5723:2022). Safe operation of AI systems is improved through:\n\n- responsible design, development, and deployment practices;\n- clear information to deployers on responsible use of the system;\n…","charCount": 807,"tokenEstimate": 202,"pageStart": 19,"pageEnd": 19,"headingPath": ["Artificial Intelligence Risk Management Framework (AI RMF 1.0)","3. AI Risks and Trustworthiness","3.2 Safe"]}],"removedHeadersFooters": ["NIST AI #-#", "AI RMF #.#", "Page #", "Categories", "Subcategories"],"warnings": [],"fullResultKey": null,"chargedEvents": 1,"scrapedAt": "2026-10-11T05:36:07.668Z"}
And this is a stretch of the markdown of that item, exactly as returned:
#### 3.2 SafeAI systems should “not under defined conditions, lead to a state in which human life, health, property, or the environment is endangered” (Source: ISO/IEC TS 5723:2022). Safe operation of AI systems is improved through:- responsible design, development, and deployment practices;- clear information to deployers on responsible use of the system;- responsible decision-making by deployers and end users; and- explanations and documentation of risks based on empirical evidence of incidents.Different types of safety risks may require tailored AI risk management approaches based on context and the severity of potential risks presented. Safety risks that pose a potential risk of serious injury or death call for the most urgent prioritization and most thorough risk management process.
id is derived from the URL or record you gave, so it is the same on every run. fileSha256 changes when the file does. Fields that do not apply are null.
Scanned PDFs
When no page that was read has a text layer, the item has status: "no_text_layer", isScanned: true, empty text, and a warning that says OCR is needed. When only some pages are scans, the item is ok, the text of the other pages is returned, and pagesWithoutText lists the scanned ones.
A scan that already went through OCR has a text layer, and that is what you get: as good or as bad as the OCR that made it.
Files that cannot be read
A bad file never stops the run. It gets an item with status: "failed" and an errorCode, is listed in the SUMMARY record of the run's key-value store, and is not charged.
errorCode | Meaning |
|---|---|
not_a_pdf | The link returns a web page or another kind of file. |
download_failed | The server refused, timed out, or kept failing after four attempts. |
too_large | The file is over 100 MB. |
password_required, wrong_password | The PDF needs a password to open. |
corrupt_pdf | The file is damaged beyond what the reader can repair. |
no_pages_selected | The page range lies outside the document. |
timeout, out_of_memory | The file was given up on. Running with more memory helps with the second. |
invalid_url, empty_file, record_not_found, internal_error | As named. |
How the text is rebuilt, and where that stops working
Headings are inferred from typography alone: text set larger than the body is a heading, bold text at body size on a line of its own is a lower-level heading, and section numbers such as 3.2.1 set the depth. The largest heading on the first page is level 1. This works well on papers, reports and manuals. It finds nothing in a document whose headings look like its body text, and it sometimes promotes a bold line that is not a heading, typically an author name under a title.
Paragraphs are reflowed. A word hyphenated at a line end is rejoined, and the rest of the document decides whether the hyphen stays: trans- + former becomes transformer, self- + attention stays self-attention if the document writes it that way elsewhere. A paragraph that runs into the next column or page is joined to its second half when that half starts in lower case; when it starts with a capital the two halves stay separate paragraphs.
Columns are found from the empty strips between them and read left to right, with full-width titles and footers kept out. Two-column papers and reports read correctly in our tests. Layouts with boxes, sidebars and text wrapped around pictures can come out in the wrong order.
Headers, footers and page numbers are removed only when the same line repeats at the same height in the margin of at least three pages close together. Everything removed is listed in removedHeadersFooters. A header that changes on every page is left in. The column headings of a table that continues over several pages can be removed as if they were a running header; the list shows it when that happens, and removeHeadersFooters: false turns removal off.
Lists with bullets, numbers or letters become Markdown lists, nested by indentation.
Tables are best effort. Columns of short cells with clear gaps between them become a Markdown table, including under a header cell that spans columns. Tables with ruled lines but little space, cells with wrapped text, merged cells and tables without visible column gaps come out as rows of text or as a table with misplaced cells. If you need tables as data, check them.
Footnotes stay where the page puts them, between the paragraphs around the page break, and footnote marks sit in the text as plain digits.
Not handled. Formulas come out as loose symbols. Text inside charts and diagrams comes out as stray labels and sometimes as a small meaningless table. Form field values, comments and attachments are not read. Short text set sideways or at an angle (margin stamps, watermarks, axis labels) is dropped. Right-to-left and vertical scripts are extracted but have not been tested, and their order may be wrong. A PDF's own tags and bookmarks are not used. In a document with very complex vector graphics (maps, dense plots), bold type is no longer recognised after the first such page, so later headings are found by size only.
Limits
- File size: 100 MB per PDF. Larger files are reported as
too_large. - Memory: the default is 2 GB. A 1,300-page government report (9.8 million characters) with page texts and chunks peaked at 1.1 GB; a 43 MB atlas full of vector maps peaked at 0.7 GB; ordinary papers and reports stay under 0.4 GB. The minimum setting of 1 GB is enough for documents of a few hundred pages.
- Result size: Apify dataset items hold about 9 MB. A larger result (a book with page texts and chunks) is saved whole as a JSON record in the run's key-value store; the item then leaves out the largest fields and names the record in
fullResultKey. - Stalls: a PDF that makes no progress for two minutes is given up on and reported, and the run moves on.
Speed
Text-heavy documents take about 5 to 15 milliseconds per page on one CPU core, papers full of formulas three to four times that. On our test machine two PDFs of 75 pages together took under 2 seconds end to end, and a 1,653-page statute took 8 seconds. On Apify the default memory gives a run half a CPU core, so the same work takes at least twice as long there.
Pricing
Pay per PDF: $4 per 1,000 PDFs of up to 100 pages each. There is no monthly fee.
A longer PDF counts once per started 100 pages, because reading it takes that much longer: a 250-page report is charged as 3 PDFs ($0.012), a 1,000-page book as 10 ($0.04). chargedEvents in every item shows what it cost, and maxPagesPerDocument caps it.
You pay nothing for files that fail, for links that are not PDFs, or for scanned PDFs that yield no text.
You can cap what a run may spend with the Maximum cost per run setting. The Actor stops cleanly at the limit: a long PDF is read as far as the remaining budget pays for and says so in warnings, and PDFs it did not reach are counted in the SUMMARY record.
FAQ
Can it read scanned PDFs? No. This version has no OCR. It detects scans and reports them at no charge, so you can send only those to an OCR tool.
Which library reads the PDFs? Mozilla's pdf.js. Nothing is sent to an outside service; the files are processed inside the run and are not kept after it.
How accurate is the token count? tokenEstimate and token-based chunk sizes assume four characters per token. That is close for English prose with OpenAI and Anthropic models and rougher for code and for other languages. Leave some margin under your model's limit.
Does a chunk ever cross a section? No. A chunk ends at the next heading. Sections longer than the chunk size are split between paragraphs, then between sentences, and the following chunk starts with the last sentences of the one before, up to chunkOverlap.
Why is my heading a paragraph, or my paragraph a heading? Headings are guessed from font size and weight; see above. The headings array shows what was recognised.
Something looks wrong. Open an issue on the Issues tab with the run ID and, if you can, the PDF's URL.