PDF Text Extractor with OCR | ๐ต$0.005 per PDF
Pricing
from $3.20 / 1,000 pdfs
PDF Text Extractor with OCR | ๐ต$0.005 per PDF
[๐ต $0.005 per PDF + $0.0004 per page ยท no start fee] Any PDF URL into clean, layout-aware text that reads multi-column pages in the right order. Whole document and page by page, plus metadata, bookmarks and links. Optional OCR reads scanned pages, billed only for the pages it reads.
Pricing
from $3.20 / 1,000 pdfs
Rating
0.0
(0)
Developer
WebData Labs
Maintained by CommunityActor stats
1
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
PDF Text Extractor
Turn any list of PDF URLs into clean, layout-aware text plus full metadata, bookmarks and links - and get told which files are scanned images before you wonder why the text is empty.
Most PDF extractors hand you one welded string per file. Columns run into each other, every paragraph break is gone, and a scanned document comes back as an empty text field with nothing to explain why. This Actor rebuilds the page layout instead of concatenating glyph runs: lines stay lines, paragraphs stay paragraphs, and each page arrives separately with its own character and word counts. Files that carry images but no text are labelled needsOcr: true rather than returned silently blank, encrypted files are opened when you supply the password and reported honestly when you do not, and anything that failed to download comes back as an error row you are never charged for. Scanned pages can be read too: turn ocr on and only the pages that actually needed it are recognised and billed. No API key, no browser, no proxy.
โ What you get / โ what this isn't
| โ What you get | โ What this isn't |
|---|---|
Layout-aware text with real line and paragraph breaks | Not items.join(" "), which welds every line into one blob |
| Multi-column pages in reading order, one column finished before the next | Not two columns spliced into each other line by line |
pages[] with the text, charCount, wordCount and size of each page | Not a single document string you have to re-split yourself |
needsOcr set when unreadable pages actually carry images | Not an empty text field with no explanation |
| Optional OCR that reads scanned pages, billed only for pages it actually read | Not a scan you have to route to a second tool and pay for twice |
passwords tried against encrypted files, with wasDecrypted recorded | Not a hard failure on every protected PDF |
outline[] - the bookmark tree with the page each entry points to | Not metadata only, with the document structure thrown away |
links[] - every clickable link annotation with its page | Not URLs you have to regex back out of the text |
chunks[] - page-tagged chunks sized for embeddings, on request | Not a raw dump you must split before it reaches a vector store |
| Priced per PDF and per page, so short files stay cheap | Not one flat fee that makes a 1-page invoice subsidise a 300-page manual |
| No start fee, and failed downloads are never charged | Not a per-run fee on top of the per-file price |
๐ Why use this Actor
- The layout is rebuilt, not guessed. PDF stores positioned glyph runs, not lines. The page is first cut into blocks: a vertical gutter separates columns, a full-width horizontal band separates a banner headline from the body beneath it. Only inside a block are runs bucketed into lines, ordered left to right, and their vertical gaps turned back into paragraph breaks. That is the difference between text an LLM can read and text it has to untangle.
- Two-column papers come out readable. Grouping lines by baseline alone, which is what almost every extractor does, splices the left column's line into the right column's sentence. Here each column is finished before the next one starts, and words broken across a line break are rejoined.
- Scanned files are named as scanned. A page under 40 characters is probed for image operators, and
needsOcris only set when those pages really do carry images. A blank cover sheet is not flagged, and a 300-page scan is not delivered as a successful empty run. - Password-protected PDFs are supported. Put candidate passwords into
passwordsand each file is opened with the first one that works.isEncryptedandwasDecryptedare recorded either way, so you can tell "protected and opened" from "protected and skipped". - Every failure is a row, and a free one. A 404, a timeout, an HTML login page served instead of a file, a corrupt document: each returns a row with
status: "error"and a specificerrorType, the run keeps going, and no charge event fires. You pay for PDFs that were actually read. - Ready for RAG without a second step. Set
chunkCharsand the text comes back pre-split on paragraph boundaries, never across a page, with the page number on every chunk so your citations survive. - Competitor input JSON just runs.
urls,startUrls,pdfUrls,fileUrls,links,maxResults,maxItemsandmaxUrlsare all accepted, so a config copied from another PDF Actor's docs does not fail validation on the first run. - Nothing to tune. No proxy, concurrency, retry or timeout fields. Pacing, retries with backoff and download limits are handled internally and included in the price. It runs on plain HTTP with no browser, which is why it is fast and cheap.
๐ฅ Who it's for
AI and RAG engineers, legal and compliance teams, financial and procurement analysts, researchers and developers who have a list of PDF links and need the text behind them in a structured, machine-readable shape.
- Load contracts, filings, tenders or manuals into a vector store with page-level citations intact.
- Feed report text straight into an LLM for summarisation, classification or extraction.
- Screen a document set, separate the digital-native files from the scans, and read the scans in the same run when you want them.
- Build a searchable index of every PDF a website publishes, with title, author and creation date attached.
- Pull the bookmark tree out of long documents to map their structure before reading any of them.
- Extract every outbound link from a document set for reference or citation analysis.
Example tasks
Ready-made configurations, each set up for one job:
- Extract text from a list of PDF URLs
- Convert PDFs to text for RAG and embeddings
- Extract PDF metadata: title, author and dates
- Find scanned PDFs that need OCR
- Extract text from password-protected PDFs
- Extract text page by page from a PDF
- Extract bookmarks and the table of contents from a PDF
- Extract all hyperlinks from PDF documents
- Extract text from research paper PDFs
- Bulk PDF text extraction to CSV or Excel
โ๏ธ How to extract text from a PDF
- Open the Actor and paste your PDF links into
urls. Any public HTTP or HTTPS link works, including files on S3, Google Cloud Storage, an Apify key-value store or your own server. Redirects are followed. - Leave
maxPdfsat 100 for a first run. It is a budget guard, not a hard ceiling: raise it for a big batch, or set 0 to process the whole list. - Leave
includePageTexton if you want each page separately. Turn it off for smaller rows when the whole-documenttextis all you need. - If any of your files are protected, add the candidate passwords to
passwords. - Click Start. Results appear in the Documents view on the Output tab, with the full text in the Extracted text view, and export as JSON, CSV, Excel, XML or RSS.
Load a document set into a vector store
Set chunkChars to your embedding model's window, for example 1200. Each row then carries a chunks array where every chunk is a paragraph-aligned slice of a single page, tagged with page and index. Feed chunks straight into your embedding step: the page number travels with the text, so answers can cite a page rather than a file.
Process a folder of PDFs on a schedule
Create a Saved Task with your URL list, then add a schedule on the Actor's Schedules tab. Because sha256 is returned for every file, you can detect a document that changed between runs without diffing its text.
๐ฅ Input
{"urls": ["https://arxiv.org/pdf/1706.03762","https://www.irs.gov/pub/irs-pdf/fw9.pdf"],"maxPdfs": 100,"includePageText": true,"includeOutline": true,"includeLinks": true,"chunkChars": 1200,"ocr": false,"ocrLanguage": "eng","maxOcrPages": 50,"passwords": []}
urls- the PDF links to read. Accepts full URLs and bare domains with a path. AliasesstartUrls(plain strings or{ "url": ... }objects),pdfUrls,fileUrlsandlinksare merged into the same list and deduplicated.maxPdfs- budget cap on how many PDFs this run processes. Default100. Set0for no cap. Aliases:maxResults,maxItems,maxUrls.includePageText- add thepagesarray with per-page text and counts. Defaulttrue.includeOutline- extract the bookmark tree intooutline. Defaulttrue.includeLinks- extract clickable link annotations intolinks. Defaulttrue.chunkChars- optional. Split the text into page-tagged chunks of about this many characters for embeddings. Default0, which skips chunking. Values below 200 are raised to 200.ocr- read scanned pages by recognising the page image. Defaultfalse, because OCR costs extra and most PDFs do not need it. Only pages with no text layer that actually contain a scan are processed and billed.ocrLanguage- language of the scanned text:eng,deu,fra,spa,ita,por,nld,pol,ukrorrus. Language names (English,German) and two-letter codes (en,de) are accepted too. Defaulteng. Only used whenocris on.maxOcrPages- cap on scanned pages read per file, so one long scan cannot surprise you. Default50. Set0for no cap. When the cap stops the work the row carriesocrTruncated: true.passwords- optional list of passwords tried in order against encrypted files. Default[].
๐ค Output
One row per input URL. A short sample of the most-scanned fields:
| url | fileName | pageCount | title | charCount | needsOcr | isEncrypted | status |
|---|---|---|---|---|---|---|---|
| https://arxiv.org/pdf/1706.03762 | 1706.03762v7.pdf | 15 | null | 39720 | false | false | ok |
| https://www.irs.gov/pub/irs-pdf/fw9.pdf | fw9.pdf | 6 | Form W-9 (Rev. March 2024) | 37704 | false | false | ok |
| https://example.com/scan.pdf | scan.pdf | 6 | imagemagick-images | 0 | true | false | ok |
| https://example.com/missing.pdf | null | null | null | null | null | null | error |
Every row carries the full field set below.
| Field | Type | What it is |
|---|---|---|
recordType | string | Always pdf. |
url | string | The URL exactly as you supplied it. |
finalUrl | string | The URL the download landed on after redirects. |
status | string | ok or error. |
httpStatus | integer | HTTP status of the download. |
fileName | string | From Content-Disposition if the server sent one, otherwise from the URL path. |
fileSizeBytes | integer | Size of the downloaded file. |
contentType | string | Content type the server declared. |
sha256 | string | Hex digest of the file, for change detection and deduplication. |
pageCount | integer | Pages in the document. |
processedPages | integer | Pages actually read. Equals pageCount unless the per-file time budget was hit. |
truncatedAtPage | integer | The page reading stopped at, or null when the whole file was read. |
pdfVersion | string | PDF format version, for example 1.7. |
isEncrypted | boolean | The file uses PDF encryption. |
wasDecrypted | boolean | One of your passwords was needed to open it. |
isLinearized | boolean | The file is optimised for fast web viewing. |
hasAcroForm | boolean | The file contains form fields (AcroForm or XFA). |
hasSignature | boolean | The file carries a digital signature. |
title, author, subject, keywords | string | Document information dictionary, null when absent. |
creator, producer | string | The application that authored the content and the one that wrote the PDF. |
language | string | Document language tag, for example en-US. |
createdAt, modifiedAt | string | Creation and modification timestamps, normalised to ISO 8601 UTC. |
text | string | The whole document as layout-aware text, pages joined by a blank line. |
textTruncated | boolean | The text hit the 3,000,000 character row limit and was cut. |
charCount | integer | Characters extracted across all pages. |
wordCount | integer | Whitespace-separated tokens in text. |
avgCharsPerPage | integer | charCount divided by processedPages. |
emptyTextPages | integer | Pages that yielded no text at all. |
needsOcr | boolean | The document has unreadable pages that carry images and were not read. With ocr on it means "still unread", so a fully OCR'd scan comes back false. |
ocrUsed | boolean | At least one page was read by OCR. |
ocrPages | integer | Pages OCR actually read, and exactly what you are charged for. |
ocrCharCount | integer | Characters that came from OCR rather than from the file's own text layer. |
ocrConfidence | integer | Average confidence over the OCR'd pages, 0-100. Low values mean a poor scan - see the caveats. |
ocrLanguage | string | The language OCR ran with, or null when it was not used. |
ocrTruncated | boolean | maxOcrPages stopped the work with scanned pages left over. |
pages | array | { page, text, charCount, wordCount, width, height, rotation, ocrApplied, ocrConfidence } per page, when includePageText is on. |
outline | array | { level, title, page, url } per bookmark, nested depth flattened into level. |
links | array | { page, url } per clickable link annotation, deduplicated. |
linkCount | integer | Number of entries in links. |
chunks | array | { index, page, charCount, text } per chunk, when chunkChars is set. |
chunkCount | integer | Number of entries in chunks. |
error | string | Human-readable failure message, null on success. |
errorType | string | One of not_found, http_error, timeout, network_error, not_a_pdf, file_too_large, password_required, invalid_pdf, parse_error. |
durationMs | integer | Download plus extraction time for this file. |
extractedAt | string | ISO 8601 timestamp of when the row was produced. |
The run also writes a summary record to the key-value store with requested, extracted, failed, pagesRead, charactersExtracted, needsOcr, encrypted and ocrPages counts.
๐ต How much does it cost?
Charges drop with your Apify plan and there is no start fee: a small amount per PDF opened, plus a per-page amount for what is actually inside it. A one-page invoice costs a fraction of a 300-page manual, which a flat per-file price cannot express. OCR is charged separately and only when you turn it on.
| FREE | BRONZE | SILVER | GOLD | PLATINUM | DIAMOND | |
|---|---|---|---|---|---|---|
| per PDF | $0.005 | $0.0044 | $0.0038 | $0.0032 | $0.0026 | $0.002 |
| per page | $0.0004 | $0.00035 | $0.0003 | $0.00025 | $0.0002 | $0.00016 |
| per OCR page | $0.010 | $0.0088 | $0.0076 | $0.0064 | $0.0052 | $0.004 |
The OCR charge applies only to pages OCR actually read: a page that already had text, a blank page, and a page the engine could not read cost you nothing extra. With ocr off it never applies at all.
What that works out to on the free tier:
| Document | Here | An Actor at $0.01 per PDF + $0.0005 per page |
|---|---|---|
| 1-page invoice | $0.0054 | $0.0105 |
| 26-page report | $0.0154 | $0.0230 |
| 142-page manual | $0.0618 | $0.0810 |
Rows that failed to download or parse carry status: "error" and are not charged - neither the file nor its pages - so a stale link list costs you nothing beyond the platform compute. Pages that could not be read inside the per-file time budget are not charged either.
Apify platform usage (compute and data transfer) is billed separately by Apify and is small: there is no browser and no proxy, and a 15-page research paper is read in well under a second.
๐ Run it on the Apify platform
Schedule the Actor for a recurring document sweep, call it from the Apify API or any Apify client, and export results as JSON, CSV, Excel, XML or RSS. Datasets connect to Make, Zapier, Google Sheets, Airtable, Slack and webhooks, so extracted text can land in a spreadsheet, a warehouse or a vector store without glue code.
Use with AI agents via MCP
The Actor is exposed over Apify's MCP server, so an agent can call it directly and read a PDF on its own.
Claude Code
$claude mcp add apify --transport http https://mcp.apify.com --header "Authorization: Bearer <YOUR_APIFY_TOKEN>"
Cursor or Claude Desktop - add to your MCP config:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com","headers": { "Authorization": "Bearer <YOUR_APIFY_TOKEN>" }}}}
Then ask the agent to run webdata_labs/pdf-text-extractor with the PDF URL. Structured parameters exist precisely so an agent can fill urls: ["https://..."] without constructing anything.
Use this Actor in n8n
n8n ships an Apify node. Add it, authenticate with your Apify API token, choose Run Actor, select webdata_labs/pdf-text-extractor, and paste the input JSON above. Use Run Actor and get dataset to get rows back in the same step, then feed them into an embeddings node, a Sheets node or a database. Pair it with an n8n Schedule Trigger for a recurring document sweep.
โ ๏ธ Limits and caveats
Written so these do not surprise you later.
- OCR accuracy depends on the scan, and the row tells you which you got. On a clean 150 DPI scan expect around 95% of words read correctly. A skewed, low-resolution or heavily compressed scan does measurably worse - in our own testing a 1.5 degree skew at 110 DPI dropped a dense form to 56%, while ordinary body text held 87%.
ocrConfidencemoves with it (88 versus 60 in those two cases), so check that field before trusting the text downstream. OCR also does not reconstruct table structure. - Stamped scans are handled. Court ECF headers, fax banners and Bates numbers leave a scanned page with a little real text over an image of the actual content. Those pages are OCR'd too, and the result is only kept when it is substantially richer than the stamp, so a page whose text layer was already fine is never charged for.
- OCR is slow compared with reading a text layer. Roughly 5-10 seconds per scanned page, against a fraction of a second for a normal page.
maxOcrPages(default 50) keeps a single long scan from taking over the run. - With
ocroff, scanned pages are flagged, not read. You getneedsOcr: true,emptyTextPagesandcharCount: 0, and you are still charged for the document, because it was opened and analysed. - Column detection is geometric, not perfect. Columns are found from the whitespace gutter between them, so a page whose gutter is filled edge to edge by a wide figure or a full-width table can still fall back to reading across. Ordinary two- and three-column layouts, forms and reports come out in reading order.
- Words broken across a line break are rejoined without the hyphen.
informa-plustionbecomesinformation, which is what a search or embedding step needs. The cost is that a genuine compound broken at exactly the line end loses its hyphen too:position-wisecan come back aspositionwise. Line-break hyphenation is far commoner than line-final compounds, so this is the better default, but it is a trade-off rather than a free win. - Tables come out as text, not as a grid. Cell contents appear in reading order with spacing preserved. There is no table structure detection and no cell array, because a wrong table shape is worse than none.
- With
ocroff, only the first 5 low-text pages are probed for images. That is what feedsneedsOcr. A 300-page scan whose first five pages are genuinely blank covers would not be flagged. Withocron the probe reaches as far asmaxOcrPages, so the flag is exact over everything OCR was allowed to consider.emptyTextPagesandavgCharsPerPageare exact for every page and are the fields to check if that edge case matters to you. - Encrypted files need your password. Without a working entry in
passwordsthe row comes backerrorType: "password_required"and is not charged. There is no password cracking and none will be added. - Files are capped at 50 MB and 4 minutes of reading each, and the time budget grows when OCR is on, because recognition is the slow part. A file over the size limit returns
file_too_large. A document too long to finish inside the time budget returns the pages it read, withtruncatedAtPageset andprocessedPagesbelowpageCount, rather than failing the whole row. - A row's text is capped at 3,000,000 characters by Apify's dataset item size limit.
textTruncatedtells you when that happened;pages[]still carries what was read. titleis oftennull, and that is the file's fault. Many PDFs, including most LaTeX output, carry no document title. The Actor reports what the file contains and never invents a title from the first line.- XFA-only forms give little text. Some form PDFs, particularly government ones, keep their content in an XFA layer.
hasAcroFormwill betruewhile the text is thin. The static page text is what comes back. - No login-gated files. A URL behind a session or a paywall returns whatever the server sends an anonymous client, usually an HTML page, which is reported as
not_a_pdfand not charged.
Is it legal to extract text from a PDF?
This Actor downloads files from URLs you supply, using the same anonymous HTTP request a browser makes, and reads the text layer the file already contains. It does not log in, does not bypass access controls, and does not crack encryption: a protected document is opened only with a password you provide yourself.
Reading a document you are entitled to access is not in itself a legal problem, and in the US the Ninth Circuit's decision in hiQ Labs v. LinkedIn held that accessing publicly available pages does not violate the Computer Fraud and Abuse Act. What you do next is regulated separately. Copyright stays with whoever wrote the document, so extracted text is licensed material, not free content: quoting, redistributing or training on it may need permission. If the documents contain personal data, GDPR and similar laws apply to your processing of it. Where the files sit behind a site's terms of service, those terms govern your relationship with that site. None of this is legal advice - if your use case is redistribution or model training rather than internal analysis, talk to a lawyer.
๐งฉ Related Actors
Part of a document and public-record stack. Each one produces the links or the context this Actor turns into text.
- EU Tenders API - TED procurement notices come with PDF specification documents attached. Pull the notices, then run their document URLs through this Actor to search the specifications themselves.
- US Building Permits API - permit records that reference filed plan sets and notices as PDFs.
- SEC Financials API - EDGAR filings, where the narrative sections you actually want to read are the attached documents.
- Spain BOE Subastas API - auction announcements published as official PDF bulletins.
- Bulk URL Status Checker - run a document link list through it first to drop the dead links, so every URL you pay to extract actually resolves.
- Website Contact Extractor - the same sweep across a site's HTML pages, for the contact details that live outside its documents.
- Sanctions Screening API - screen the names and entities you pull out of contracts and filings against consolidated sanctions lists.
โ FAQ
Do I need an API key or an account with anyone?
No. The Actor fetches the file over plain HTTPS and reads it locally. There is no third-party PDF service, no API key and no per-page conversion fee.
Am I charged for PDFs that fail?
No. A charge event fires only when a document was opened and its text delivered. Dead links, timeouts, HTML pages served instead of a file, corrupt documents and password-protected files you could not open all return a row with status: "error" and cost nothing.
Can it read scanned documents?
Yes, with ocr turned on. Pages that have no text layer but do carry a scan are recognised from the page image, and ocrPages, ocrConfidence and the per-page ocrApplied flag tell you exactly what was read and how well. Only those pages are charged for. Leave ocr off and the Actor simply labels them instead: needsOcr: true with charCount: 0, so you can route them elsewhere.
Why is text empty when the PDF clearly has words in it?
Check needsOcr. If it is true, the words are pixels, not text, which is the case for anything produced by a scanner or a photo - turn ocr on to read them. If it is false and the text is still empty, the file may use a broken or non-embedded font encoding; send us the URL and we will look.
How does it handle password-protected PDFs?
Put the candidates in passwords. Every encrypted file is tried against each in order and opened with the first that works, with wasDecrypted: true recorded on the row. Files that none of them open return password_required and are not charged.
How big and how long can a PDF be?
Up to 50 MB per file, and up to 4 minutes of reading per file. Long documents that exceed the time budget return the pages read so far with truncatedAtPage set, instead of failing.
Is the text good enough for an LLM or a vector store?
That is what the layout reconstruction is for. Lines and paragraphs survive, each page is available separately, and setting chunkChars returns chunks that never cross a page boundary and carry the page number for citations.
Does it keep the tables?
Table text comes back in reading order with spacing preserved, but there is no cell structure. If you need real tables, extract the text here and run a table parser over the page you care about.
Can I extract only the metadata and skip the text?
The metadata always ships. Setting includePageText: false drops the per-page array and keeps the whole-document text plus every counter, which is the smallest useful row.
What happens to a URL that redirects?
Redirects are followed and the landing URL is recorded in finalUrl, so you can see when a permalink resolved somewhere unexpected.
๐ ๏ธ Support
Something wrong or missing? Open an issue on the Actor's Issues tab with the run URL, the input you used and what you expected. Include the PDF URL when a specific file misbehaves; that is almost always enough to reproduce it.
โญ Rate this Actor
If this saved you an afternoon, please leave a rating on the Reviews tab. Review count is the main trust signal buyers use on the Store, and the feedback decides what gets built next. If something is broken, please open an issue first so it can be fixed rather than rated low: bugs get fixed faster than they get argued about.
Changelog
0.2 - 2026-08-07
- Priced per PDF plus per page instead of a flat per-file fee, so short documents cost less and long ones pay their way.
- Multi-column pages are now segmented into blocks and read column by column instead of across the gutter. Words broken across a line break are rejoined.
0.1 - 2026-08-07
- First release. Layout-aware text extraction, per-page output, document metadata with ISO 8601 dates, bookmark outline with page numbers, link annotations, scanned-document detection, password support, optional RAG chunking, and per-row error handling with no charge on failure.