How to extract text from hundreds of PDFs, including scanned ones, in one run
Updated 2026-10-28 ยท Tool used: PDF Text Extractor & OCR - Bulk PDF and Image to Text on Apify ยท Price: $1.50 per 1,000 PDFs + $0.002 per OCR page; failed downloads, non-PDF links, password-protected files and blank pages are free
Reports, invoices, tender documents and archives usually arrive as a pile of PDFs, and some of them are scans with no selectable text at all. Online converters handle one file at a time, and sending every page through a paid OCR API is wasteful when most PDFs already contain exact text.
This guide shows how to pass a list of PDF or image links and get one row per file with the full text, page count and the method used. Digital PDFs are read from their text layer (fast, no OCR errors) and only pages without text go through Tesseract OCR, so you only pay the OCR price where it was needed. Everything runs inside the actor, with no outside OCR service.
Open PDF Text Extractor & OCR on Apify โSteps
- Add direct links to
fileUrls, or setdatasetIdto collect PDF and image links from another run (for example a sitemap export filtered to.pdf). - Leave
ocrModeonautoand chooselanguagesfor non-English scans. Turn onincludePagesif you want per-page text for RAG chunking and page citations. - Run it and export the dataset as JSON or CSV, or load it straight into LangChain, LlamaIndex or a vector database.
Example input
{
"url": "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf",
"status": "ok",
"fileType": "pdf",
"method": "ocr",
"pageCount": 1,
"pagesProcessed": 1,
"textLayerPages": 0,
"ocrPages": 1,
"blankPages": 0,
"ocrLanguages": [
"eng"
],
"producer": "libtiff / tiff2pdf - 20150912",
"createdAt": "2016-01-19T12:38:47.000Z",
"chars": 4508,
"words": 728,
"text": "The LinnSequencer\n32 Track MIDI Sequence Recorder\n\nThe LinnSequencer is a state-of-the-art composition and performance tool for the professional music ...",
"textTruncated": false,
"charged": {
"documents": 0,
"ocrPages": 1
},
"httpStatus": 200,
"fileSizeBytes": 103856,
"extractedAt": "2026-10-19T07:28:29.277Z"
}
Example output (one item, shortened)
{
"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
"status": "ok",
"fileType": "pdf",
"httpStatus": 200,
"error": null,
"downloadMs": 107,
"fileSizeBytes": 140815,
"extractMs": 49,
"method": "text-layer",
"pageCount": 6,
"pagesProcessed": 6,
"pagesNotProcessed": 0,
"textLayerPages": 6,
"ocrPages": 0,
"blankPages": 0,
"ocrLanguages": [],
"title": "Form W-9 (Rev. March 2024)",
"author": "SE:W:CAR:MP",
"subject": "Request for Taxpayer Identification Number and Certification",
"keywords": "Fillable",
"creator": "Designer 6.5",
"producer": "Designer 6.5",
"createdAt": "2024-03-06T13:18:13.000Z",
"modifiedAt": "2024-03-06T13:18:13.000Z",
"encrypted": false,
"pdfVersion": "1.7",
"chars": 37820,
"words": 6279,
"text": "W-9\n\nForm\n(Rev. March 2024)\nDepartment of the Treasury\nInternal Revenue Service\n\nRequest for Taxpayer\nIdentification Number and Certification\n\nGive fo ...",
"textTruncated": false,
"charged": {
"documents": 1,
"ocrPages": 0
},
"extractedAt": "2026-10-19T07:28:25.882Z"
}
Every run's dataset can be downloaded as JSON, CSV, Excel, XML or HTML from the Apify Console, or via https://api.apify.com/v2/datasets/<datasetId>/items?format=csv.
Run it from code
Python
from apify_client import ApifyClient # pip install apify-client
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run_input = {
"url": "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf",
"status": "ok",
"fileType": "pdf",
"method": "ocr",
"pageCount": 1,
"pagesProcessed": 1,
"textLayerPages": 0,
"ocrPages": 1,
"blankPages": 0,
"ocrLanguages": [
"eng"
],
"producer": "libtiff / tiff2pdf - 20150912",
"createdAt": "2016-01-19T12:38:47.000Z",
"chars": 4508,
"words": 728,
"text": "The LinnSequencer\n32 Track MIDI Sequence Recorder\n\nThe LinnSequencer is a state-of-the-art composition and performance tool for the professional music ...",
"textTruncated": false,
"charged": {
"documents": 0,
"ocrPages": 1
},
"httpStatus": 200,
"fileSizeBytes": 103856,
"extractedAt": "2026-10-19T07:28:29.277Z"
}
run = client.actor("tinyrex/pdf-ocr-text-extractor").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item)JavaScript / Node.js
import { ApifyClient } from 'apify-client'; // npm i apify-client
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('tinyrex/pdf-ocr-text-extractor').call({
"url": "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf",
"status": "ok",
"fileType": "pdf",
"method": "ocr",
"pageCount": 1,
"pagesProcessed": 1,
"textLayerPages": 0,
"ocrPages": 1,
"blankPages": 0,
"ocrLanguages": [
"eng"
],
"producer": "libtiff / tiff2pdf - 20150912",
"createdAt": "2016-01-19T12:38:47.000Z",
"chars": 4508,
"words": 728,
"text": "The LinnSequencer\n32 Track MIDI Sequence Recorder\n\nThe LinnSequencer is a state-of-the-art composition and performance tool for the professional music ...",
"textTruncated": false,
"charged": {
"documents": 0,
"ocrPages": 1
},
"httpStatus": 200,
"fileSizeBytes": 103856,
"extractedAt": "2026-10-19T07:28:29.277Z"
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);curl (no SDK)
# Runs the actor and returns the results directly (CSV here; use format=json for JSON)
curl -X POST "https://api.apify.com/v2/acts/tinyrex~pdf-ocr-text-extractor/run-sync-get-dataset-items?format=csv" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://raw.githubusercontent.com/ocrmypdf/OCRmyPDF/v16.0.0/tests/resources/ccitt.pdf", "status": "ok", "fileType": "pdf", "method": "ocr", "pageCount": 1, "pagesProcessed": 1, "textLayerPages": 0, "ocrPages": 1, "blankPages": 0, "ocrLanguages": ["eng"], "producer": "libtiff / tiff2pdf - 20150912", "createdAt": "2016-01-19T12:38:47.000Z", "chars": 4508, "words": 728, "text": "The LinnSequencer\n32 Track MIDI Sequence Recorder\n\nThe LinnSequencer is a state-of-the-art composition and performance tool for the professional music ...", "textTruncated": false, "charged": {"documents": 0, "ocrPages": 1}, "httpStatus": 200, "fileSizeBytes": 103856, "extractedAt": "2026-10-19T07:28:29.277Z"}' > results.csv
Get your API token in Apify Console โ Settings โ API & Integrations. The free Apify plan includes monthly credit that is enough to try this.
Use it from an AI agent (MCP)
The Apify MCP server exposes this actor as a tool for Claude, Cursor, VS Code, n8n and other MCP clients. Add this to your client's MCP config (or use the OAuth flow at mcp.apify.com instead of a token):
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=tinyrex/pdf-ocr-text-extractor",
"headers": {
"Authorization": "Bearer <YOUR_APIFY_TOKEN>"
}
}
}
}
Then ask in plain language, e.g. "Bulk pdf to text for this list and give me a table".
Pricing
Pay per result: $1.50 per 1,000 PDFs + $0.002 per OCR page; failed downloads, non-PDF links, password-protected files and blank pages are free. You can cap the cost of every run in the run options.
FAQ
How do I extract text from a scanned PDF?
Add its link and keep OCR mode on auto. Pages without a text layer are rendered and read by Tesseract; the row shows method: ocr and the number of ocrPages.
How can I tell if a PDF is scanned or has real text?
Each row reports textLayerPages, ocrPages and a method of text-layer, ocr or mixed.
Does it work on images like PNG or JPG?
Yes. PNG, JPEG, TIFF, WebP, GIF and BMP links are OCR'd as one page each.
Are my documents sent to a third-party API?
No. Download, text extraction (poppler) and OCR (Tesseract) all run inside the actor's container, and results stay in your own Apify storage.
Try it on Apify โMore guides
- Bulk tech stack lookup: a Wappalyzer / BuiltWith alternative for thousands of domains
- How to scrape Greenhouse, Lever and Ashby jobs (plus Personio, Teamtailor, Recruitee)
- How to export all products from any Shopify store to CSV or JSON
- How to scrape jobs from Workday career sites (myworkdayjobs.com)
- How to export products from any WooCommerce store to CSV or JSON
- How to export EU public tenders from TED (Tenders Electronic Daily) to CSV or JSON
- How to scrape remote jobs from Himalayas, Remote OK, We Work Remotely and more in one run
- How to export open EU funding calls (Horizon Europe, Digital Europe, cascade funding) to CSV or JSON
- How to turn any RSS or Atom feed into JSON or CSV (bulk, with monitoring)
- How to extract emails, phone numbers and social links from a list of company domains
- How to take bulk website screenshots from a URL list and save them to CSV or Google Drive
- How to export US federal (SAM.gov) and UK (Contracts Finder) tenders to CSV or JSON
- How to look up French companies by SIREN or SIRET and build a B2B list from Sirene
- How to look up Norwegian companies by organisation number and build lead lists from Brreg
- How to download SEC filings (10-K, 10-Q, 8-K, Form 4) by ticker and search EDGAR full text
- How to validate NPI numbers in bulk and build US healthcare provider lists from NPPES
- How to look up hundreds of UK companies on Companies House at once (profile, officers, PSC)
- How to look up Finnish companies by Y-tunnus and build B2B lead lists from the YTJ register
- How to extract all URLs from a website's sitemap (and find broken links)
- How to transcribe hundreds of audio and video files to text and subtitles with Whisper
- How to clean an email list before a campaign: bulk MX, disposable and typo check
- How to get Dutch public tenders from TenderNed into a spreadsheet, with daily alerts
- How to audit titles, meta descriptions, H1s and canonicals for hundreds of pages at once
- How to find every broken link and redirect on a website, with the pages to fix
- How to validate LEI codes and find the LEI for a list of companies in bulk
- How to run Lighthouse on hundreds of URLs and get Core Web Vitals in one table