PDF Text Extractor - Upload & Base64 avatar

PDF Text Extractor - Upload & Base64

Pricing

from $3.00 / 1,000 pdf extracteds

Go to Apify Store
PDF Text Extractor - Upload & Base64

PDF Text Extractor - Upload & Base64

Extract text and metadata from digital PDFs sent as a URL, an upload, or base64. For n8n, Make, backend and RAG pipelines that need PDF text without OCR. Extract only documents you have the right to read. This Actor does not bypass a login, paywall, CAPTCHA, or other access control.

Pricing

from $3.00 / 1,000 pdf extracteds

Rating

0.0

(0)

Developer

Vadim Bezrukov

Vadim Bezrukov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Extract text and metadata from digital PDFs sent as URLs, uploads, or base64. Built for n8n, Make, backend and RAG pipelines that need a simple PDF-to-text step without OCR infrastructure.

Give the Actor a public PDF link, one uploaded file, or base64 from an email or webhook. Each document comes back as one Dataset row: full text, optional per-page text, page count, file size, SHA-256, basic metadata and an explicit status. The Console prefill downloads one short public sample, W3C's dummy PDF, so the first run has a row to inspect.

A second run is the next batch. Point it at the next file that arrived. This Actor does not watch a URL for changes.

Latest notes are in the changelog.

Why use it

Automation builders already receive PDFs as attachments and links. The job is to turn a digital PDF that already has a text layer into structured text for an LLM, a CRM, a sheet or a vector pipeline.

This Actor is the small version of that step. It accepts an upload and base64 as well as a URL, and it stops when the file has no text layer. It does not reconstruct tables, rewrite the text as Markdown, or call an OCR engine.

Supported input

Documents are read in this order: urls, then base64Pdfs, then pdfFile. One run can mix them, up to 200 documents. A base64 batch is limited to 32 files so the input fits in the default 1024 MB. A larger batch should be public URLs or the single upload.

InputUse it when
urlsThe PDF is already on a public http or https URL.
pdfFileYou upload one PDF in the Console, or the platform gives you the uploaded-file URL.
base64PdfsA webhook, n8n, Make, or your backend has the PDF bytes. A data:application/pdf;base64, prefix is accepted.

To send only an upload or base64, omit urls or set it to []. If the platform fills urls with the sample and the request also has base64Pdfs or pdfFile, the sample is skipped. RUN_SUMMARY then sets droppedDefaultSampleUrl. The sample still runs when it is the only input.

Private hosts, localhost, link-local addresses, cloud metadata hosts and URLs with embedded passwords are rejected. Ports other than 80 and 443 are rejected.

Quick start

{
"urls": [
"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
],
"includePages": true,
"maxFileSizeMb": 5,
"maxPagesPerPdf": 500
}

Open the run's Dataset. A digital sample returns status: "SUCCESS" and the page text. Check RUN_SUMMARY in the Key-Value Store for counts, the charge total and the next step.

Examples

Three saved examples follow the jobs above. Replace the sample, then read the result table.

One public PDF starts from the W3C dummy file. The table shows the file name, page count and text. A scan is not listed in that table: its status is NO_TEXT_LAYER.

A list of PDFs starts from three arXiv papers. Each file has its own status, so one bad link stays in the list.

A base64 PDF is the input for n8n, Make, or a webhook. The sample is one page, and its text is "Digital PDF for the upload check." Replace the file name and the payload.

Sample output

The row below shows the Dataset contract. Hashes and timestamps are illustrative. A live run replaces them from the file it read. Query values in source_url are redacted, and a short hash is appended.

{
"record_type": "PDF_RESULT",
"status": "SUCCESS",
"input_type": "URL",
"input_index": 0,
"source_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"file_name": "dummy.pdf",
"sha256": null,
"file_size_bytes": 13264,
"page_count": 1,
"extracted_page_count": 1,
"text": "Dummy PDF file",
"pages": [
{"page": 1, "text": "Dummy PDF file"}
],
"metadata": {
"title": null,
"author": null,
"subject": null,
"creator": null,
"producer": null,
"creation_date": null,
"modification_date": null
},
"character_count": 14,
"word_count": 3,
"error": null,
"duplicate_of": null,
"charged": true,
"source": "user_pdf",
"source_id": "0000000000000000000000000000000000000000000000000000000000000000",
"schema_version": 1,
"processed_at": "2026-09-23T00:00:00Z",
"scraped_at": "2026-09-23T00:00:00Z",
"fingerprint": "1111111111111111111111111111111111111111111111111111111111111111"
}

text is null when the row is not a successful extraction. An empty string is not used as a stand-in for success.

n8n, Make and backend

Put the PDF bytes in base64 and start a run with your own Apify token. The body below is the Actor input. Replace the data with your payload.

{
"urls": [],
"base64Pdfs": [
{
"filename": "attachment.pdf",
"data": "<base64>"
}
],
"includePages": true
}
POST https://api.apify.com/v2/acts/automa-flow~pdf-text-extractor/runs
Authorization: Bearer <your Apify token>
Content-Type: application/json

In n8n or Make, map the attachment's base64 into base64Pdfs[0].data. When the run finishes, read the Dataset items. Keep the row when status is SUCCESS. Route NO_TEXT_LAYER to an OCR step of your own. This Actor will not do that step for you.

Before the call, set maxTotalChargeUsd to at least 0.01 + 0.003 * PDFs you expect to succeed at the default 1 GB memory.

Batch example

{
"urls": [
"https://example.com/report-a.pdf",
"https://example.com/report-b.pdf"
],
"base64Pdfs": [
{
"filename": "notes.pdf",
"data": "<base64>"
}
],
"includePages": false,
"maxFileSizeMb": 5,
"maxPagesPerPdf": 500
}

includePages: false still returns the full text. It omits the per-page array so a large batch stays smaller. One bad file does not drop the others. Identical bytes in the same run are extracted once: the later row is DUPLICATE and is not charged again.

Statuses

StatusMeaning
SUCCESSText was extracted. This is the only status that can incur pdf-extracted.
DUPLICATESame SHA-256 as an earlier document in this run. Not parsed or charged again.
NO_TEXT_LAYERThe file opened and no page had extractable text. OCR was not run.
INVALID_PDFThe bytes are not a readable PDF.
PASSWORD_PROTECTEDThe PDF asks for a password. Unlocking is not supported.
TOO_LARGEThe file is over maxFileSizeMb, or the run reached the 1 GB decoded-byte ceiling.
TOO_MANY_PAGESThe PDF has more pages than maxPagesPerPdf.
DOWNLOAD_FAILEDThe URL did not return the file after the retry budget.
INVALID_URLThe URL is not a public http(s) address on port 80 or 443.
FAILEDThe parser, the 2,000,000 character text cap, or the spending limit stopped this document. error says which.

NO_TEXT_LAYER is a completed observation. It is not a failed download and it is not charged. A whole run fails only for invalid input, a pricing or billing state that cannot be resolved safely, or a failure to persist output. A batch of bad PDFs still finishes with one row per input.

RUN_SUMMARY lists the counts, whether the spending limit stopped the run, and nextAction.

Pricing

Pay per event. At the default 1 GB of memory:

EventPrice
Actor start$0.01 per run (one event per GB of memory, minimum one)
pdf-extracted$0.003 per unique PDF with SUCCESS

At the default 1 GB, one successful PDF is $0.013, ten are $0.04, and a full run of 200 unique successes is $0.61. Those totals are the $0.01 start plus $0.003 for each success. Platform usage (compute, data transfer, storage) is billed separately by Apify.

There is no pdf-extracted charge for retries, duplicate documents, failed downloads, invalid PDFs, password-protected PDFs, files over the size, page or text limit, or NO_TEXT_LAYER. The Actor start charge and platform usage still apply.

Set maxTotalChargeUsd before an API or agent run. A successful PDF is saved and charged in the same Dataset write. If the limit refuses that charge, the success row is not saved. Later documents that were not already extracted return FAILED with no text. A later copy of a PDF already extracted in the run stays DUPLICATE and is not charged again. Rows already saved stay saved. Raise the limit and run the refused inputs again.

This is a flat event price. It is not a claim to be the lowest price on the Store.

Limits

LimitValue
Documents per run200 total across all inputs, including at most 32 base64 files and one upload
File size1 to 5 MB (maxFileSizeMb)
Pages per PDF1 to 500 (maxPagesPerPdf)
Decoded bytes per run1 GiB guard. 200 URL files at 5 MiB each are 1000 MiB, so the file-size and count limits bind first. Base64 is capped at 32 files so the input strings fit in the default 1024 MB.
Extracted text2,000,000 characters. A larger extraction returns FAILED and is not charged.
Dataset itemAbout 8.5 MB of UTF-8. Page text is dropped first. If the row is still too large, that document is FAILED and is not charged.
Download attempts3, for DNS failures, network errors, HTTP 429 and HTTP 5xx. Each attempt has a 60 second limit, including a slow drip of bytes. Exceeding that limit is DOWNLOAD_FAILED and is not retried. The wait between retries is cut to the time left before the run deadline.
Accepted responseHTTP 200 without HTTP content compression. The Actor requests Accept-Encoding: identity; gzip, deflate and Brotli responses are rejected as DOWNLOAD_FAILED. Compression inside the PDF is supported.
Parser time90 seconds per document, or the time left before the run deadline, whichever is shorter. The parser runs in a separate process. A timeout stops that process, returns FAILED for that file, and the next document starts in a new process. It is not charged.
Run deadlineNew work stops about 30 seconds before the Actor timeout to save remaining statuses and RUN_SUMMARY. Retry waits and parser time use only the time that remains. If the Dataset cannot confirm all remaining rows before the finalization deadline, the run fails with SYSTEM_FAILURE and preserves confirmed results.
Redirects5, and each target is checked again
Memory1024 MB default. Actor start scales with memory above that.

There is no concurrency input. Downloads use a small internal pool. Parsing runs one document at a time so a batch stays within the memory limit.

If RUN_SUMMARY.finalizationIncomplete is true, inputs still counts every submitted document and datasetRows counts confirmed writes. Check unconfirmedInputIndices against the Dataset before retrying. uncertainWriteInputIndices identifies a write whose confirmation was lost, so its rows may already exist. The Actor does not replay those writes automatically.

Privacy and security

Use this Actor only for documents you have the right to extract. Do not use it to bypass a login, a paywall, a CAPTCHA, or any other access control.

These files may contain confidential or personal information. Extraction runs inside the Actor. Bytes are not sent to an external PDF, OCR, or LLM API. The Actor log does not include document text, base64, or uploaded bytes. Signed URL query values are redacted before a row is stored.

The Dataset and the run input live in your Apify storage. How long they remain follows your Apify account and storage settings. This page does not claim a GDPR or HIPAA certification.

Only public http and https URLs are fetched. Local, private, link-local and cloud-metadata addresses are blocked, including after redirects. A public URL can still point at a file the caller is not allowed to copy. That residual risk is moderate: private hosts are blocked, and the Actor does not decide whether a reachable public file may be extracted.

API and MCP

After Store publication, agents can discover the Actor through Apify MCP search. Running it uses your own Apify account and token. A private Actor requires authenticated direct access.

Direct MCP endpoint:

https://mcp.apify.com?tools=automa-flow/pdf-text-extractor

When an agent should call it: you have a digital PDF as a URL, an upload, or base64, and you need the text plus a status before writing a record, a chunk, or a sheet row.

When an agent should not call it: the PDF is a scan, you need OCR, table reconstruction, Markdown layout, or a password removed.

Ask it to extract the text and return the status, page count and text. Then read RUN_SUMMARY before fetching a large Dataset. If chargeLimitReached is true, raise maxTotalChargeUsd and rerun the documents that were not charged.

The output links are:

  • Dataset items: results in the run output
  • Summary: Key-Value Store record RUN_SUMMARY
  • Charges: Key-Value Store record BILLING_RECEIPT

Technical details

Text comes from the PDF text layer through PyMuPDF, on the Actor itself. The full text value is the page texts joined with a newline after CR LF and CR are turned into LF. NUL bytes are removed so the row can be stored as JSON. Other characters are left as the extractor returned them. The Actor does not claim that the text preserves visual layout.

Each acquired file is hashed with SHA-256. The same hash later in the run reuses the first result. source_id is that hash when the bytes were read. fingerprint hashes the semantic fields and leaves out the clock and the charged flag, so a later batch can be compared.

The receipt records the document hash before the charged Dataset write. The success row and the pdf-extracted event are one write: if the spending limit refuses the event, that row is not stored. If the charge state cannot be resolved, the run stops and does not charge that document again on a replay.