How it will work: 3 steps
- Follow us on ApifyThe tool is built and waits in the queue for the Apify Store. Apify is the cloud platform where our tools run.
- Paste your input and press StartNo code needed: you fill in a form (the input). It already has a working example. For example:
https://arxiv.org/pdf/1706.03762in PDF URLs. Each start is one run. - Download the resultsWhen the run ends, the results are in a table (the dataset). Open Output, press Export and pick Excel or CSV. JSON and the API are there too.
This tool is an Apify Actor: a small program that runs on Apify. You can also run it on a schedule, for example every Monday. New to it? Read how it works, step by step and the 8 words you need first.
What it does
Convert public PDF links into clean Markdown for LLMs and RAG: real headings, lists, GitHub-style tables and ready-made chunks with page numbers and token counts. Scanned pages are read with OCR.
- Downloads the PDF from the public URL.
- Reads the text layer of each page and keeps the layout: big text becomes
#headings, bullets become lists, and grids of words become Markdown tables. - Reads scanned pages (pages without a text layer) with OCR.
- Cuts the Markdown into chunks for embeddings. A new heading always starts a new chunk, and every chunk knows its pages and the headings above it.
- Saves one row per PDF in the Dataset.
Good for
- Load research papers, reports or manuals into a vector database for RAG.
- Give an AI agent the text of a PDF as Markdown, with tables it can read.
- Pull tables out of public reports and forms as Markdown or as cells.
- Read old scanned documents that have no text layer.
Not a good fit
- PDFs behind a login, a paywall or a private network. Only public
http(s)links work. - Password-protected PDFs. The Actor reports them and does not try to open them.
- Formulas, charts and pictures. The text around them is kept, the images are not described.
- Uploading files from your computer. Put the file at a public URL first.
Who uses it
RAG developers
Load papers, reports and manuals into a vector database with page numbers for citations.
Data analysts
Pull tables out of public reports and paste them into a sheet.
AI agent builders
Give a model the text of a PDF in a format it reads well.
Price
Prices will be shown after release.
Questions
Why is this better than plain PDF text?
You keep the structure. Headings, lists and tables stay readable for a model.
Does it describe charts and images?
No. The text around them is kept, but pictures and formulas are not described.
Can it open a password PDF?
No. It reports the file as protected and does not try to open it.
How big can a PDF be?
Up to 300 pages and 100 MB per file. You can send up to 100 links per run.
Do I need a login or an API key for the PDFs?
No. The Actor reads public links. You only need an Apify account to run it.
How is this different from a plain PDF text extractor?
A plain extractor gives one long text. This Actor keeps headings, lists and tables as Markdown and also returns chunks with pages and the headings above them, so you can load them into a vector database without your own splitter.
Why did I pay for OCR on a PDF?
Some pages had no text layer (scans), so they were read by OCR. Set ocr: "off" if you never want OCR.
Is the output always perfect?
No. Text-layer PDFs are converted well; complex tables and poor scans can have mistakes. Check pages[].ocrConfidence and the warnings.
Do you keep my documents?
No. The PDF files are deleted at the end of the run. The rows stay in your own Apify storage.
Details
Step by step on Apify
- Open the Input tab.
- Keep the example link in PDF URLs (
https://arxiv.org/pdf/1706.03762) or add your own PDF links, one per line. - Keep the default settings: chunks of about 500 tokens, OCR Auto.
- Click Start.
- Open the Output tab. The Documents view shows the Markdown and tables; the Chunks view shows the chunks.
Use cases
| Task | Input | Key fields | Next step |
|---|---|---|---|
| RAG over research papers | arXiv links in pdfUrls | chunks[].text, headingPath, pageStart | Embed chunks into your vector database; cite pages in answers. |
| Table extraction from reports | report links, maxPages: 300 | tables[].cells, tables[].page | Load cells into a sheet or a database. |
| Agent reads a PDF | one link from the conversation | markdown, title, tokenEstimate | Put the Markdown into the model context. |
| Digitize old scans | scan links, ocr: "auto" | markdown, pages[].ocrConfidence | Check pages with low confidence by hand. |
Tips for better results
- Use the ready chunks as they are. A new heading always starts a new chunk.
- For big scanned files, raise the run timeout or pick only some pages. OCR takes about 10 seconds per page.
- Treat the token count as a guess. It is characters divided by 4, not a real tokenizer.
Limits and good to know
If the run is close to its timeout, the Actor stops taking new pages, saves what it has with status: "partial", and lists the documents it did not reach with DEADLINE_REACHED. You pay only for pages that were read.
- No login, cookies, paywalls or private networks. No password cracking
- No image, chart or formula description. Text in pictures is read only by OCR on scanned pages
- Tables without lines are found by the columns of words. Merged or very complex cells can shift
- Tokens are an estimate (characters / 4), not the count of a specific tokenizer
- Files are deleted when the run ends. Only the Markdown and the summary are kept in your storage
- Text PDFs: about 3 seconds for a 15-page paper; 100 PDFs (535 pages) in about 45 seconds; a 300-page report with 1,281 tables in about a minute
- OCR: about 10 seconds per page at 1 GB, up to 30 seconds for dense pages. A 36-page scan takes about 6 minutes. The default run timeout is 30 minutes (about 90–180 OCR pages); for bigger scans raise the timeout or use
pages - Up to 100 URLs per run, up to 300 pages and 100 MB per document
For developers
Sample result
One result from a real test run, in JSON. Real values change from run to run. In Excel or CSV each field is a column.
{
"url": "https://arxiv.org/pdf/1706.03762",
"finalUrl": "https://arxiv.org/pdf/1706.03762",
"status": "complete",
"title": "Attention Is All You Need",
"pageCount": 15,
"pagesProcessed": 15,
"pagesTruncated": false,
"markdown": "<!-- page 1 -->\n\n…\n\n# Attention Is All You Need\n\n…\n\n<!-- page 2 -->\n\n## 1 Introduction\n\nRecurrent neural networks, long short-term memory [13] and gated recurrent [7] neural networks …",
"markdownUrl": null,
"charCount": 41793,
"tokenEstimate": 10449,
"tables": [
{
"index": 1,
"page": 6,
"rows": 6,
"columns": 4,
"markdown": "| Layer Type | Complexity per Layer | Sequential | Maximum Path Length |\n| --- | --- | --- | --- |\n…",
"cells": [["Layer Type", "Complexity per Layer", "Sequential", "Maximum Path Length"], ["Self-Attention", "2 · O(n d)", "O(1)", "O(1)"]]
}
],
"chunks": [
{
"index": 8,
"text": "### 3.2 Attention\n\nAn attention function can be described as mapping a query and a set of key-value pairs to an output …",
"pageStart": 3,
"pageEnd": 4,
"headingPath": ["Attention Is All You Need", "3 Model Architecture", "3.2 Attention"],
"tokens": 136,
"charCount": 543
}
],
"chunkSize": 500,
"ocrUsed": false,
"textPages": 15,
"ocrPages": 0,
"ocrLanguages": null,
"pages": [
{ "pageNumber": 1, "method": "text", "charCount": 2886, "tables": 0, "ocrConfidence": null, "error": null }
],
"fileSizeBytes": 2215244,
"contentType": "application/pdf",
"metadata": { "creator": "LaTeX with hyperref", "producer": "pdfTeX-1.40.25", "pdfVersion": "1.5" },
"encrypted": false,
"error": null,
"warnings": [],
"fetchedAt": "2026-10-15T18:15:47.458Z",
"processedAt": "2026-10-15T18:15:47.633Z"
}Data fields (27)
| Field | Type | Meaning |
|---|---|---|
url | string | Input URL as given. |
finalUrl | string | URL after redirects; null when the file was not downloaded. |
status | string | complete, partial (some pages failed or were not processed), not_found (HTTP 404/410) or error. |
title | string | Title from the PDF metadata, else the top-level heading of the text layer (never from OCR); null when neither exists. |
pageCount | integer | Total pages in the PDF; null if unknown. |
pagesProcessed | integer | Pages read successfully. |
pagesTruncated | boolean | true when maxPages cut the page selection. |
markdown | string | Markdown of all processed pages: # headings, lists, GFM tables. Each page starts with an <!-- page N --> marker. null on error, or when the row would exceed the Dataset item limit (then the full Markdown is at markdownUrl). |
markdownUrl | string | Link to the full Markdown file in the run key-value store when it was too large for the row; null otherwise. |
charCount | integer | Characters in markdown. |
tokenEstimate | integer | Approximate tokens of markdown (characters / 4). |
tables | array | Detected tables: index, page, rows, columns, markdown (GFM) and cells (rows of strings; null only when dropped to fit the row size limit). |
chunks | array | Retrieval chunks: index, text (Markdown), pageStart, pageEnd, headingPath (headings above the chunk), tokens (approximate), charCount. |
chunkSize | integer | Target chunk size used, in approximate tokens. |
ocrUsed | boolean | true when at least one page was read with OCR. |
textPages | integer | Pages taken from the PDF text layer. Charged as one document event per started 50 of them (1–50 pages: 1 event, 51–100: 2). |
ocrPages | integer | Pages read with OCR, each charged as one ocr-page event per selected OCR language. |
ocrLanguages | array | OCR languages used, when any page was OCRed; null otherwise. |
pages | array | Per page: pageNumber, method (text, ocr or none), charCount, tables, ocrConfidence (0–100, OCR only), error. |
fileSizeBytes | integer | Downloaded size in bytes. |
contentType | string | Content-Type header sent by the server. |
metadata | object | PDF metadata as reported by the file: author, subject, keywords, creator, producer, creationDate, modificationDate, pdfVersion. Not verified. |
encrypted | boolean | true when the PDF is encrypted (it may still be readable without a password). |
error | object | Error for this row: {code, message}, e.g. NOT_FOUND, FILE_TOO_LARGE, UNSUPPORTED_FILE_TYPE, PDF_PASSWORD_REQUIRED, DOWNLOAD_TIMEOUT, DEADLINE_REACHED, OUTPUT_TOO_LARGE; null when there is none. |
warnings | array | Notes such as truncated pages or pages without a text layer. |
fetchedAt | string | When the file was downloaded (ISO 8601); null when it was not. |
processedAt | string | When the row was written (ISO 8601). |
Input settings (8)
The main fields of the input form. With the API you send them as JSON.
| Setting | Name in the API | Type | Example value |
|---|---|---|---|
| PDF URLs required | pdfUrls | array | ["https://arxiv.org/pdf/1706.03762"] |
| Chunk size (tokens) | chunkSize | integer | 500 |
| Chunk overlap (tokens) | chunkOverlap | integer | 50 |
| OCR | ocr | string | "auto" |
| OCR languages | languages | array | ["eng"] |
| Pages | pages | string | |
| Max pages per document | maxPages | integer | 50 |
| Max file size (MB) | maxFileSizeMb | integer | 50 |
Use cases with this tool
Each guide shows all the steps and the other tools.