PDF Text Extractor + OCR for Scanned PDFs
Pricing
from $1.50 / 1,000 pdf extracteds
PDF Text Extractor + OCR for Scanned PDFs
Extract text from PDFs by URL, with automatic OCR for scanned pages. One flat row per URL, never null, only successful PDFs billed. Fast, low-memory engine; Google Drive, Dropbox and arXiv links accepted. Built for pipelines and AI agents.
Pricing
from $1.50 / 1,000 pdf extracteds
Rating
0.0
(0)
Developer
Sumitr Mardy
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
Give it PDF links, get back the text. Pages that are scans or photos are read with OCR automatically; pages that already have text are never OCRed. One flat row per URL, every field always present, never null — built to be called from pipelines, n8n/Make/Zapier flows and AI agents.
$2 per 1,000 PDFs. $5 per 1,000 OCR pages. Failed downloads, broken files and PDFs with no text are free.
Why this one
- It does not fall over on scans. OCR runs one page at a time with bounded memory. On a 41-file batch with 120 scanned pages, peak memory was 541 MB at the default 2 GB.
- One row for every URL you send. A 404, a password-protected file and a corrupt PDF each still come back as a row with
status: "failed"and a machine-readablesubStatus. You never have to diff your input against the output to find what went missing. - You pay for text, not for attempts. A PDF is billed only when text came out of it. An OCR page is billed only when OCR recovered more text than the page already had.
- Fast. The text layer is read with PDFium, the PDF engine inside Chrome: 1–8 ms per page. An 838-page book took 18 seconds end to end, including the 52 MB download.
- Share links just work. Google Drive, Dropbox, OneDrive and arXiv
/abs/links are turned into direct downloads. If a host blocks the request (401/403/407/429), the download is retried once through Apify Proxy.
Input
| Field | Default | What it does |
|---|---|---|
urls | — | PDF links, one per line. Required. |
ocrMode | auto | auto OCRs only pages with no text layer that contain an image. off never OCRs. |
ocrLanguage | eng | Tesseract language code; join several with + (eng+fra). 30 languages are built in, including vie, chi_sim, jpn, kor, ara, hin. |
maxOcrPagesPerPdf | 50 | Cost cap for huge scanned books. Pages over the cap come back empty and are listed in notes. |
pageRange | all | 1-5, 8, 12- style. A range that selects no page fails that row, uncharged. |
includePages | false | Adds pages[] with per-page text and an ocr flag, for page-level citations. |
chunkSize / chunkOverlap | 0 / 200 | RAG chunks of about chunkSize characters, each with pageStart and pageEnd. 0 turns chunking off. |
password | — | Used for every PDF in the run that needs one. |
maxFileSizeMb | 100 | Larger files fail as too_large, uncharged. |
{"urls": ["https://arxiv.org/abs/1706.03762", "https://www.irs.gov/pub/irs-pdf/f1040.pdf"],"ocrLanguage": "eng","chunkSize": 1500}
Output
One row per input URL, in completion order. Use inputIndex to line rows up with your input.
{"inputIndex": 0,"url": "https://arxiv.org/abs/1706.03762","resolvedUrl": "https://arxiv.org/pdf/1706.03762.pdf","fileName": "1706.03762.pdf","status": "ok","subStatus": "text_layer","error": "","pageCount": 15, "pagesExtracted": 15, "ocrPages": 0, "scannedPages": 0,"charCount": 39598, "wordCount": 6099,"text": "Attention Is All You Need\nAshish Vaswani∗\nGoogle Brain\n…","textTruncated": false, "fullTextUrl": "","pages": [], "chunks": [],"title": "", "author": "", "subject": "", "keywords": "", "creator": "LaTeX with hyperref", "producer": "pdfTeX-1.40.25","creationDate": "2024-04-10T21:11:43Z", "modDate": "2024-04-10T21:11:43Z","pdfVersion": 15, "isEncrypted": false, "fileSizeBytes": 2215244,"notes": [], "processingMs": 218}
Strings are "" when absent, numbers 0, arrays []. No field is ever null or missing.
Status
status | subStatus | Charged |
|---|---|---|
ok | text_layer — all text came from the PDF itself | |
ok | ocr — some or all pages were read by OCR | PDF + each OCR page |
ok | ocr_incomplete — some scanned pages were not read (page cap, cost limit, OCR off, OCR failure); see notes | PDF + each OCR page |
no_text | no_text_layer, scanned_ocr_off, ocr_found_nothing | free |
failed | http_404 (any http_<code>), not_a_pdf, too_large, download_timeout, download_error, password_required, wrong_password, unsupported_encryption, corrupt_pdf, empty_document, bad_page_range, page_range_out_of_bounds, invalid_url, cost_limit_reached, extraction_error | free |
Very large documents: if a row would exceed Apify's dataset item limit, the full text is saved to the run's key-value store and linked from fullTextUrl. text keeps the first 1,000,000 characters and textTruncated is true.
Pricing
| Event | Price | When |
|---|---|---|
pdf-extracted | $0.002 | A PDF returned text (status: "ok") |
ocr-page | $0.005 | A scanned page whose text was recovered by OCR |
The only other charge is Apify's standard Actor start fee, $0.00005 per GB of memory ($0.0001 per run at the default 2 GB). Prices drop on higher Apify plans: down to $1.50 per 1,000 PDFs and $3.50 per 1,000 OCR pages on Gold. A run stops starting new PDFs when your maximum cost per run is reached, and marks the rest cost_limit_reached, uncharged.
What it does not do (yet)
Honest limits, so you can decide before you run it:
- Plain text, not Markdown. Line breaks are kept and pages are joined with a blank line, but headings and lists are not marked up.
- Tables come out as text, one table row per line. Cells are not split into columns.
- Text order follows the PDF's own content order. For most papers, reports and forms that is reading order. Some designed layouts (magazines, brochures) can interleave columns.
- OCR is Tesseract with fast models. It is good on clean scans and weaker on handwriting, very low-resolution photos and heavily stylised fonts.
- Text-based PDFs only need small memory. Scans need CPU. The default 2 GB OCRs about one page every 4–5 seconds. For big scanned batches, 8 GB runs two OCR pages at a time.
Use it from AI agents (MCP)
This Actor is a tool on the Apify MCP server, so Claude, Cursor, VS Code or any MCP client can call it to read a PDF. Load only this tool: a short tool list keeps the agent from picking the wrong one.
Recommended: hosted server, OAuth sign-in (nothing secret on disk). Add to your MCP client config (claude_desktop_config.json, .cursor/mcp.json, …):
{"mcpServers": {"pdf-text-extractor": {"url": "https://mcp.apify.com?tools=sumitr_mardy/pdf-text-extractor"}}}
With an API token instead (clients without OAuth, or unattended setups):
{"mcpServers": {"pdf-text-extractor": {"url": "https://mcp.apify.com?tools=sumitr_mardy/pdf-text-extractor","headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }}}}
Local server (stdio):
{"mcpServers": {"pdf-text-extractor": {"command": "npx","args": ["-y", "@apify/actors-mcp-server@latest", "--tools", "sumitr_mardy/pdf-text-extractor"],"env": { "APIFY_TOKEN": "YOUR_APIFY_TOKEN" }}}}
The hosted server also gives the agent this Actor's output schema, so it knows every field before the first call. apify mcp install <client> writes the config for you if you use the Apify CLI. Get a token at Apify Console → API & Integrations, and never commit it.
Tips for agents:
- "Summarise this PDF": pass the link in
urls. Readtext, and checkstatusfirst:no_textandfailedrows carry the reason insubStatusanderror. - "Find where the report says X": set
includePages: trueand quotepageNumberback to the user, or setchunkSize: 1500and search thechunks. - Long scanned documents: set
pageRangeor a lowermaxOcrPagesPerPdf. OCR takes about 4–5 seconds a page at the default memory. - The MCP
call-actortool waits up to 45 seconds. A longer run keeps going: fetch the results withget-dataset-itemsand the run'sdatasetId. - Every row has the same keys and never a
null, so an agent can readtext,pageCountortitlewithout a guard. An emptytitlemeans the PDF has no title in its metadata, not that the read failed.
Use it from code
from apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("sumitr_mardy/pdf-text-extractor").call(run_input={"urls": ["https://www.irs.gov/pub/irs-pdf/f1040.pdf"],})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["status"], row["subStatus"], row["charCount"])
curl -X POST "https://api.apify.com/v2/acts/sumitr_mardy~pdf-text-extractor/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \-H "Content-Type: application/json" \-d '{"urls": ["https://arxiv.org/abs/1706.03762"]}'