PDF Text Extractor — PDF to Text & Markdown API | $3/1k PDFs avatar

PDF Text Extractor — PDF to Text & Markdown API | $3/1k PDFs

Pricing

from $2.40 / 1,000 pdf processeds

Go to Apify Store
PDF Text Extractor — PDF to Text & Markdown API | $3/1k PDFs

PDF Text Extractor — PDF to Text & Markdown API | $3/1k PDFs

Extract text and Markdown from PDF URLs in bulk, with document info (title, dates, page count, producer) and per-page text. Pure JavaScript, no OCR, no API key. Broken links, non-PDFs and scanned files are free rows. $3 per 1,000 PDFs.

Pricing

from $2.40 / 1,000 pdf processeds

Rating

0.0

(0)

Developer

Raffy

Raffy

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

What does PDF Text Extractor do?

PDF Text Extractor turns PDF files into text and Markdown. Paste a list of PDF links (reports, papers, contracts, manuals, invoices, government forms), upload files, or send them as base64, and get back one clean row per PDF with the full text, an LLM-ready Markdown version and the document's own info: title, page count, creation date, producer software and more. For RAG, turn on RAG chunks and get token-sized chunks with page numbers, section headings and overlap, ready for a vector database. Password-protected PDFs open with your password, and text tables come back as rows of cells and Markdown tables. It works as a PDF to text API and PDF to Markdown converter you can call from code, a schedule or an AI agent: no API key, no browser, nothing to install.

Typical use cases:

  • RAG and LLM pipelines: get ready-made chunks (text, pageStart, pageEnd, section, tokenCount, chunkId) for Pinecone, Qdrant, Weaviate, pgvector, LangChain or LlamaIndex, or feed whole-document Markdown into your own chunker.
  • Document search and archiving: index the text of hundreds of PDFs from your website, data room or document portal.
  • Research and analysis: pull the text of papers, filings, standards and policy documents for summarising or classification.
  • Data entry and compliance: read the text of forms and contracts, and check document info (dates, producer, encryption, form fields) in bulk.
  • AI agents: a cheap PDF reader tool with plain inputs and self-describing rows.

Why use this PDF text extractor?

  • Bulk by default. Hundreds of PDF URLs in one run, downloaded in parallel; every PDF is one row.
  • Text and Markdown in one pass. Plain text keeps the PDF's lines; Markdown rebuilds headings from font sizes, bullet and numbered lists and wrapped paragraphs, and removes page numbers and the running headers and footers repeated across pages.
  • RAG chunks with citations. One row per chunk of about chunkSizeTokens tokens (default 500), split at paragraphs and sentences, with a configurable overlap, the page numbers each chunk comes from and the heading it sits under. A PDF of up to 50 chunks costs the same as in the default mode.
  • Files, not only links. Upload PDFs in Apify Console, point at records in your key-value stores (read with your own run token, so private stores work), or send base64 from your code.
  • Password-protected PDFs. Give the password once (stored encrypted, never logged or written out); a wrong password is a free wrong_password row.
  • Text tables. With extractTables, tables in the text layer become a tables array (rows of cells) and Markdown tables, so numbers stay in their columns.
  • Per-page text when you need it. Turn on includePageText for a pages array (still one result per PDF), or splitByPage for one row per page.
  • Honest results, honest billing. Every row has a status (ok, not_found, error) and, when something went wrong, an errorType. A broken link, a web page instead of a PDF, a password-protected, damaged, oversized or scanned PDF is one free row, and the rest of the batch still runs. Only PDFs whose text was extracted are charged.
  • Simple, flat price. $3 per 1,000 PDFs (up to 100 pages each by default), whatever the page count.
  • Share links just work. Google Drive, Dropbox and GitHub file links and arXiv abstract pages are turned into their download links automatically.

What data can PDF Text Extractor extract?

One row per PDF (default):

FieldTypeDescription
fileNamestringFile name from the server's Content-Disposition header or the URL
titlestringTitle from the PDF's document info (or XMP metadata)
pageCountintegerTotal pages in the PDF
pagesExtractedintegerPages read for this row (after pageRange and maxPages)
extractedPagesstringWhich pages were read, e.g. 1-5, 8
pageNumberintegerPage number, only on rows written with splitByPage
charCountintegerCharacters of extracted text
wordCountintegerWords of extracted text
needsOcrbooleantrue when the text layer is missing (scanned or image-only pages)
pagesWithoutTextintegerPages read that had no text layer
truncatedbooleantrue when maxPages or the timeout stopped the PDF early; warning says how far it got
textstringPlain text, line by line as in the PDF, blank lines between blocks and pages
markdownstringMarkdown with # headings, - and 1. lists and reflowed paragraphs
subject, keywordsstringThe PDF's document info fields (the author field is never returned: it usually holds a person's name)
creator, producerstringThe application that made the document and the PDF
creationDate, modificationDatestringISO 8601 dates in UTC
pdfVersion, languagestringe.g. 1.7, en-US
hasForms, isEncryptedbooleanFillable form fields present; permissions-only encryption (it still opens)
fileSizeBytes, contentTypeinteger, stringThe downloaded file's size and the Content-Type the host sent
pagesarrayWith includePageText: [{ pageNumber, text, markdown, charCount, hasText }]
tables, tableCountarray, integerWith extractTables: [{ pageNumber, rowCount, columnCount, rows: [["Region", "Q1"], ["North", "1,200"]] }]
sourcestringbase64 or keyValueStore for files that did not come from a URL
inputUrl, finalUrlstringThe link as you typed it (when rewritten) and where redirects ended
urlstringThe PDF URL that was downloaded
statusstringok, not_found or error (see below)
errorTypestringMachine-readable reason on rows that are not ok
errorstringHuman-readable reason on rows that are not ok
scrapedAtstringISO 8601 time the PDF was processed

RAG chunk rows (ragChunks), one per chunk:

FieldTypeDescription
textstringThe chunk: Markdown (headings, lists, tables) unless Output format is plain text (chunkFormat says which)
chunkIdstringStable id: the same PDF URL gives the same ids on every run (<hash>-<index>)
chunkIndex, chunkCountintegerPosition of the chunk (from 0) and the number of chunks in the PDF
pageStart, pageEnd, pageNumbersinteger, integer, arrayThe page(s) the chunk's text comes from, for citations
sectionstringThe nearest heading above the chunk
tokenCountintegerEstimated tokens (about 4 characters of English per token, 1 per CJK character)
fileName, title, pageCount, urlThe PDF the chunk belongs to (on every chunk); the other document info fields are on chunk 0

Result status (tri-state output)

statuserrorTypeMeaningCharged?
ok-Text was extractedYes, one result (RAG chunks: one per started 50 chunks of a PDF)
not_foundhttp_404, http_410No file at the URLNo
not_foundno_textThe PDF (or page) has no text layer: scanned or image-only; needsOcr is trueNo
not_foundno_pages_selectedpageRange selects no page of this PDFNo
errornot_pdfThe URL returned a web page or another file type, not a PDFNo
errortoo_largeOver maxFileSizeMb (checked before downloading when the host sends the size)No
errorpassword_protectedThe PDF needs a password to open and none was givenNo
errorwrong_passwordThe password given does not open the PDFNo
errorinvalid_inputA base64Files / fileUploads entry could not be usedNo
errormalformedThe file is damaged or not a valid PDFNo
errortimeoutThe download or the reading ran out of timeNo
errorblocked, rate_limited, http_error, network, download_failedThe host refused or failed the download (after retries)No
erroroutput_too_largeThe text is larger than one dataset row can hold (9 MB); use splitByPageNo

The run itself still succeeds when some PDFs fail, so one bad link never costs you the rest of the batch.

How to use the PDF to text converter

  1. Click Try for free and paste your PDF links into PDF URLs, one per line, or upload files under Upload PDFs or key-value store records.
  2. Pick the Output format: text and Markdown (default), text only, or Markdown only.
  3. Optional: set a Page range such as 1-10, turn on Include per-page text, One row per page, RAG chunks or Extract tables, or enter a PDF password.
  4. Click Start. A typical PDF takes a second or two to read; the default example (two government PDFs, 34 pages) finishes in under 20 seconds.
  5. Download the results as JSON, CSV, Excel or HTML, or read them through the Apify API.

From code, call the Actor with the Apify API or the Apify client for JavaScript or Python and pass the same input as JSON.

How much does it cost to extract text from PDFs?

Pay-per-event pricing; Apify platform usage (compute) is included, so you pay only these events:

EventPrice
Actor start$0.005 per run
PDF processed (status: ok row)$0.003 per PDF ($3 per 1,000)

Examples:

  • 1 PDF: $0.005 + $0.003 = $0.008.
  • 100 PDFs of any length (up to maxPages, 100 by default): $0.005 + 100 x $0.003 = $0.305.
  • 1,000 PDFs: $3.005.
  • With One row per page, every page with text is one result: a 20-page PDF costs 20 x $0.003 = $0.06. For per-page text at the per-PDF price, use Include per-page text instead.
  • With RAG chunks, a PDF is one result per started 50 chunks: a 30-page report in 500-token chunks (about 40 chunks) is $0.003, a 100-page manual (about 140 chunks) is 3 x $0.003 = $0.009. That is $0.06 per 1,000 chunks at most.
  • Uploaded files, key-value store records and base64 files cost the same as URLs.

Broken links, non-PDF files, password-protected, damaged or oversized files and scanned PDFs without a text layer are free rows. Cap spending with Maximum rows and the run's Max total charge.

Input

FieldTypeDefaultDescription
startUrlsarraytwo example PDFsPDF links, one per line; share links are rewritten to download links
fileUploadsarraynoneUploaded files, key-value store record URLs, or storeId/key; read with your run's token
base64Filesarraynone["JVBERi0..."] or [{ "fileName": "a.pdf", "base64": "JVBERi0...", "password": "..." }]; a data URL works too
passwordstring (secret)noneOpens password-protected PDFs; never logged or output
outputFormatstringbothboth, text or markdown
splitByPagebooleanfalseOne row per page (each page with text is one result)
includePageTextbooleanfalseAdd a pages array with every page's text to each PDF row
ragChunksbooleanfalseOne row per token-sized chunk (replaces splitByPage)
chunkSizeTokensinteger500Chunk size in estimated tokens, 100 to 2,000
chunkOverlapTokensinteger50Tokens repeated from the previous chunk, 0 to 500 (at most half a chunk)
extractTablesbooleanfalseText tables as a tables array and Markdown tables
pageRangestringall pagese.g. 1-5, 8, 12- (12- = to the end, -3 = first three)
maxPagesinteger100Pages read per PDF, up to 1,000
includeMetadatabooleantrueAdd the PDF's document info fields
maxFileSizeMbinteger50Skip larger files, up to 100 MB
timeoutSecsinteger120Time allowed per PDF for downloading, and again for reading
maxItemsinteger1000Safety cap on rows written (with RAG chunks: on PDFs)
proxyConfigurationobjectoffApify Proxy, only for hosts that block direct downloads

Example input (RAG chunks, with a private file from a key-value store and one sent as base64):

{
"startUrls": ["https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf"],
"fileUploads": ["<storeId>/annual-report.pdf"],
"base64Files": [{ "fileName": "contract.pdf", "base64": "JVBERi0xLjcK...", "password": "your-password" }],
"ragChunks": true,
"chunkSizeTokens": 500,
"chunkOverlapTokens": 50,
"extractTables": true
}

Example input (one row per PDF):

{
"startUrls": [
"https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf",
"https://www.irs.gov/pub/irs-pdf/p1.pdf"
],
"outputFormat": "both",
"pageRange": "1-20",
"includePageText": true
}

Output

Real rows from the default run (2026-10-16), with text and markdown shortened:

[
{
"url": "https://www.irs.gov/pub/irs-pdf/p1.pdf",
"status": "ok",
"fileName": "p1.pdf",
"title": "Publication 1 (Rev. 9-2017)",
"pageCount": 2,
"pagesExtracted": 2,
"extractedPages": "1-2",
"charCount": 11376,
"wordCount": 1881,
"needsOcr": false,
"pagesWithoutText": 0,
"truncated": false,
"text": "Your Rights\nas a Taxpayer\n\nThe IRS Mission\n\nThe Taxpayer Bill of Rights\n\nPublication 1\n\nThis publication explains your rights as a taxpayer and the processes for examination, appeal, collection, and refunds.\nAlso available in Spanish. ...",
"markdown": "# Your Rights as a Taxpayer\n\n## The IRS Mission\n\n# The Taxpayer Bill of Rights\n\n## Publication 1\n\nThis publication explains your rights as a taxpayer and the processes for examination, appeal, collection, and refunds. Also available in Spanish. ...\n\n## 1. The Right to Be Informed\n\nTaxpayers have the right to know what they need to do to comply with the tax laws. ...",
"subject": "Your Rights as a Taxpayer",
"creator": "Adobe Illustrator CS6 (Windows)",
"producer": "Adobe PDF library 10.01",
"creationDate": "2017-10-31T16:08:08.000Z",
"modificationDate": "2017-10-31T16:16:24.000Z",
"pdfVersion": "1.4",
"hasForms": false,
"isEncrypted": false,
"fileSizeBytes": 324302,
"contentType": "application/pdf",
"scrapedAt": "2026-10-15T23:47:42.646Z"
},
{
"url": "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf",
"status": "ok",
"fileName": "NIST.CSWP.29.pdf",
"title": "The NIST Cybersecurity Framework (CSF) 2.0",
"pageCount": 32,
"pagesExtracted": 32,
"extractedPages": "1-32",
"charCount": 68724,
"wordCount": 9421,
"needsOcr": false,
"text": "National Institute of Standards and Technology\n\nThis publication is available free of charge from: https://doi.org/10.6028/NIST.CSWP.29\n\nFebruary 26, 2024\n\nThe NIST Cybersecurity\nFramework (CSF) 2.0 ...",
"markdown": "### National Institute of Standards and Technology\n\nThis publication is available free of charge from: https://doi.org/10.6028/NIST.CSWP.29\n\nFebruary 26, 2024\n\n# The NIST Cybersecurity Framework (CSF) 2.0 ...",
"keywords": "cybersecurity; Cybersecurity Framework (CSF); cybersecurity risk governance; cybersecurity risk management; enterprise risk management; Profiles; Tiers",
"creator": "Acrobat PDFMaker 23 for Word",
"producer": "Adobe PDF Library 23.8.53",
"creationDate": "2024-03-06T20:46:45.000Z",
"pdfVersion": "1.6",
"language": "EN-US",
"fileSizeBytes": 1518858,
"scrapedAt": "2026-10-15T23:47:43.244Z"
}
]

A link that is not a PDF comes back like this, free:

{
"url": "https://example.org/annual-report",
"status": "error",
"errorType": "not_pdf",
"error": "The URL returned a web page (text/html), not a PDF file. Use the direct link to the .pdf file; many sites show a viewer or download page first. Not charged.",
"contentType": "text/html"
}

Tips

  • Use Markdown for LLMs and RAG: headings and lists survive, and page numbers and the running headers and footers repeated at the top or bottom of the pages are removed.
  • Long PDFs: raise Max pages per PDF (up to 1,000) or read the part you need with Page range.
  • Need page numbers for citations? Include per-page text keeps one result per PDF and adds every page in pages.
  • Filter failures by errorType instead of parsing error; needsOcr: true marks scanned documents to send to an OCR tool.

Limitations

  • No OCR. Scanned or image-only PDFs have no text layer; they come back as free not_found rows with needsOcr: true. PDFs that already carry an OCR text layer are read normally.
  • Tables: text tables only. extractTables finds tables whose cells sit in columns separated by white space in the text layer, row by row. Tables that are images, cells drawn column by column, merged cells and very irregular layouts stay as plain text lines; a two-column page whose columns share baselines may come out as a two-column table. Check the tables you rely on.
  • Token counts are estimates. tokenCount uses about 4 characters per token for English and 1 per CJK character, not a model tokenizer; keep a margin under your embedding model's limit.
  • Running headers and footers are recognised when the same text (up to three lines) repeats at the top or bottom of at least 3 pages and 40% of the pages read; on shorter documents only page numbers are removed. The plain text field always keeps every line.
  • Headings come from font size. Bold text at body size is not detected as a heading, and images, charts and equations are not converted.
  • Reading order follows the PDF. Most PDFs (Word, LaTeX, InDesign exports) come out column by column; a PDF whose internal order is scrambled comes out scrambled.
  • Hyphenation. For TeX-made PDFs a hyphen at a line end is removed when the word continues on the next line; for other producers it is kept (high-level), which occasionally leaves a split word with a hyphen.
  • Passwords. PDFs that need a password to open are read when you give the password (password, or per file in base64Files); without it they are free password_protected rows. Permissions-only encryption is fine. The Actor never guesses or cracks passwords.
  • Base64 size. Base64 files travel inside the run input, which Apify limits in size; send large files as key-value store records instead.
  • Size and length. Up to 100 MB and 1,000 pages per PDF; one dataset row holds at most about 9 MB of text (use splitByPage for huge documents).
  • The host decides. A file behind a login, a cookie wall or an anti-bot challenge cannot be downloaded; you get a free blocked row. Try Apify Proxy for hosts that block direct downloads.

FAQ

Is there a PDF to text API I can call from my code?

Yes. Every Apify Actor is an API: start a run with your PDF links and read the dataset as JSON, CSV or Excel, or call it synchronously and get the rows in the response. The input and output are the tables above.

Can it convert PDF to Markdown for ChatGPT, Claude or a RAG pipeline?

Yes, that is what the markdown field is for: headings, lists and paragraphs are rebuilt and page furniture (page numbers, and running headers and footers repeated across pages) is removed, so the text chunks cleanly. Or let the Actor chunk it: RAG chunks writes one row per chunk with the page numbers, the section heading, an estimated token count and a stable chunkId, ready to embed.

Can I process PDFs that are not online?

Yes. Upload them in Apify Console (Upload PDFs or key-value store records), store them in a key-value store and pass storeId/key, or send them as base64 in base64Files from your code. Each file is billed like a URL.

Does it read scanned PDFs?

No. It reads the text layer that digital PDFs carry. A scanned PDF without one is reported as not_found with needsOcr: true and is not charged, so you can route it to an OCR tool.

Why is a PDF that opens in my browser reported as not_pdf or blocked?

Some sites link to a viewer or download page rather than the file, or only serve the file after a login or a challenge. Use the direct link to the .pdf file. For Google Drive, Dropbox and GitHub, paste the normal share link; it is converted for you.

Can I use it from an AI agent or MCP client?

Yes. The input has safe defaults, every row explains itself with status and errorType, and failed documents are free, so an agent can pass a list of links and read the result without special handling.

Is my document data stored or shared?

The Actor only downloads the URLs you give it, reads the key-value store records you point it at (with your own run's token) and the base64 files in your input, and writes the results to your own run's dataset in your Apify account. It does not send the documents anywhere else. A PDF password is a secret input: Apify stores it encrypted, and the Actor never logs it or writes it to the dataset.

This Actor processes only the documents you provide (links, uploaded files, key-value store records or base64) and returns what those documents contain, plus the technical document info stored in the PDF (title, dates, producer software). The PDF's author field is deliberately not returned. It does not log in, does not guess or crack passwords (it uses only the password you give it for your own documents), does not get around access controls and does not collect data from any website on its own. Documents can contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not process personal data unless you have a legitimate reason to do so, and you are responsible for having the right to process the documents you submit and for complying with applicable law.

This Actor is an independent tool. PDF is an open standard (ISO 32000); text extraction uses the open-source pdf.js library. All trademarks belong to their respective owners.