PDF Text Extractor + OCR for Scanned PDFs avatar

PDF Text Extractor + OCR for Scanned PDFs

Pricing

from $1.50 / 1,000 pdf extracteds

Go to Apify Store
PDF Text Extractor + OCR for Scanned PDFs

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

Sumitr Mardy

Maintained by Community

Actor 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-readable subStatus. 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

FieldDefaultWhat it does
urls—PDF links, one per line. Required.
ocrModeautoauto OCRs only pages with no text layer that contain an image. off never OCRs.
ocrLanguageengTesseract language code; join several with + (eng+fra). 30 languages are built in, including vie, chi_sim, jpn, kor, ara, hin.
maxOcrPagesPerPdf50Cost cap for huge scanned books. Pages over the cap come back empty and are listed in notes.
pageRangeall1-5, 8, 12- style. A range that selects no page fails that row, uncharged.
includePagesfalseAdds pages[] with per-page text and an ocr flag, for page-level citations.
chunkSize / chunkOverlap0 / 200RAG 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.
maxFileSizeMb100Larger 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

statussubStatusCharged
oktext_layer — all text came from the PDF itselfPDF
okocr — some or all pages were read by OCRPDF + each OCR page
okocr_incomplete — some scanned pages were not read (page cap, cost limit, OCR off, OCR failure); see notesPDF + each OCR page
no_textno_text_layer, scanned_ocr_off, ocr_found_nothingfree
failedhttp_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_errorfree

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

EventPriceWhen
pdf-extracted$0.002A PDF returned text (status: "ok")
ocr-page$0.005A 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. Read text, and check status first: no_text and failed rows carry the reason in subStatus and error.
  • "Find where the report says X": set includePages: true and quote pageNumber back to the user, or set chunkSize: 1500 and search the chunks.
  • Long scanned documents: set pageRange or a lower maxOcrPagesPerPdf. OCR takes about 4–5 seconds a page at the default memory.
  • The MCP call-actor tool waits up to 45 seconds. A longer run keeps going: fetch the results with get-dataset-items and the run's datasetId.
  • Every row has the same keys and never a null, so an agent can read text, pageCount or title without a guard. An empty title means the PDF has no title in its metadata, not that the read failed.

Use it from code

from apify_client import ApifyClient
client = 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"]}'