PDF to Markdown and Text: Tables, Pages, Metadata for RAG
Pricing
from $1.60 / 1,000 pdf extracteds
PDF to Markdown and Text: Tables, Pages, Metadata for RAG
PDF parser and text extractor: turn PDF URLs into clean Markdown, plain text, per-page text and detected tables. Fast, no browser, no proxy. Flags scanned pages that need OCR instead of charging for empty text. Built for RAG and document pipelines. USD 2 per 1,000 PDFs (up to 100 pages each).
Pricing
from $1.60 / 1,000 pdf extracteds
Rating
0.0
(0)
Developer
JT Palms
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
Give it PDF URLs, get one row per PDF with clean Markdown, plain text, optional per-page text and detected tables, plus metadata and an honest flag for pages that are scanned images. It runs on pure JavaScript: no browser, no proxy, no LLM, no OCR. Reading takes roughly 10 to 60 milliseconds per page depending on the machine.
USD 2 per 1,000 PDFs (up to 100 pages each), plus a tiny start fee per run. Failed downloads, non-PDF answers and PDFs without a text layer are free.
What people use it for
- RAG and document pipelines. Reports, manuals, policies and filings become Markdown with real headings, paragraphs and lists, ready to chunk and embed. Turn on
outputPagesto chunk by page, and keepheadingsandtitleas chunk metadata. - Filings and statistics. Regulator and statistics-agency PDFs often hold their numbers in tables. With
outputTableson, each table comes back as row arrays (for code) and as a Markdown table (for LLMs), with a confidence rating. - Tender and procurement documents. Pair it with Government Tenders Monitor: that actor returns
documentsUrlandnoticeUrlfor each notice, and when those links point straight at a PDF, this actor turns the document into text you can search or summarise. Schedule both. - Price lists and supplier documents. Pull the text and tables out of a supplier's PDF price list every week and compare.
- An agent tool. "Read this PDF" as one cheap, deterministic call that returns structured JSON.
Sample output
Real run on the IRS Form W-9 (https://www.irs.gov/pub/irs-pdf/fw9.pdf), default settings, markdown shortened:
{"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf","finalUrl": "https://www.irs.gov/pub/irs-pdf/fw9.pdf","httpStatus": 200,"contentType": "application/pdf","fileSizeBytes": 140815,"pageCount": 6,"pagesProcessed": 6,"title": "Form W-9 (Rev. March 2024)","titleSource": "metadata","subject": "Request for Taxpayer Identification Number and Certification","keywords": "Fillable","creatorTool": "Designer 6.5","producer": "Designer 6.5","createdAt": "2024-03-06T13:18:13.000Z","modifiedAt": "2024-03-06T13:18:13.000Z","language": "en-US","isEncrypted": false,"markdown": "Form W-9\n\n# Request for Taxpayer Identification Number and Certification\n\n...","wordCount": 6237,"charCount": 37706,"headings": [{ "level": 1, "text": "Request for Taxpayer Identification Number and Certification", "page": 1 },{ "level": 3, "text": "Part I Taxpayer Identification Number (TIN)", "page": 1 }],"links": [],"ocrNeededPages": [],"truncated": false,"notes": ["4 text item(s) at an angle (watermarks, side stamps) were left out.","11 repeated header, footer or page-number line(s) removed."],"error": null,"errorCode": null,"fetchedAt": "2026-10-06T19:27:29.851Z"}
A table from the same run mode with outputTables on (Table A-8 of a Bureau of Labor Statistics release, first rows, as it appears in the Markdown):
| Category | Sept. | Aug. | Sept. | Sept. | May | June | July | Aug. | Sept. || --- | --- | --- | --- | --- | --- | --- | --- | --- | --- || | 2025 | 2026 | 2026 | 2025 | 2026 | 2026 | 2026 | 2026 | 2026 || CLASS OF WORKER | | | | | | | | | || Agriculture and related industries | 2,209 | 2,551 | 2,636 | 2,206 | 2,261 | 2,291 | 2,216 | 2,444 | 2,596 || Wage and salary workers1 | 1,533 | 1,735 | 1,937 | 1,518 | 1,648 | 1,595 | 1,508 | 1,630 | 1,832 |
In the dataset the same table is tables[]: { "page": 2, "columns": 10, "rowCount": 26, "confidence": "high", "rows": [["Category", "Sept.", ...], ...] }. Dot leaders ("Revenue . . . . 1,200") are removed and the value after them is its own cell.
Input
| Field | What it does |
|---|---|
urls | PDF links, one per line (public http or https). Duplicates are read once. The default list is three small public PDFs from US government sites. |
outputFormat | markdown (default), text, or both. |
outputPages | Add pages[]: for every page read, its text (and Markdown unless the format is text), character count and needsOcr. |
outputTables | Detect tables and add tables[] and pipe tables in the Markdown. |
removeHeadersFooters | On by default. Drops repeated page headers and footers (text that repeats on more than 60% of the pages, also when odd and even pages differ) and bare page numbers. Needs 3 or more pages to spot repeats. |
pageRange | For example 1-5,9 or 10-. The same range is applied to every PDF. Empty reads everything up to maxPages. |
maxPages | Pages read per PDF: default 300, up to 2,000 (the prefill is 50). More pages than that set truncated: true. |
maxFileMb | Larger files are skipped and not charged. Default 50, up to 200. |
password | For encrypted PDFs you are entitled to open. Used only to open the file, never output. |
includeAuthor | Off by default. Adds author, the PDF's Author field (which can be a person's name). |
maxConcurrency | PDFs in parallel, 1 to 10 (default 4). Requests to one site are still at least one second apart. |
timeoutSecs | Per file: time to download it, and again to read it. At the limit you get the pages read so far with truncated: true. |
Output fields
| Field | Meaning |
|---|---|
url, finalUrl, httpStatus, contentType, fileSizeBytes | The request and the answer. |
pageCount, pagesProcessed | Pages in the PDF, and pages read. |
title, titleSource | The PDF's Title metadata (metadata). If it is missing or looks like a file name ("Microsoft Word - report.docx"), the first top-level heading is used (heading). |
subject, keywords, creatorTool, producer, createdAt, modifiedAt, language | PDF metadata, or null. Dates are ISO 8601. |
author | Only with includeAuthor. |
isEncrypted | The file is encrypted (some files are marked encrypted but open without a password, as in any PDF viewer). |
markdown, text | The document, as requested by outputFormat. |
wordCount, charCount | Size of the extracted text. |
headings | [{ level, text, page }]. |
links | Web links (http and https) found in the PDF's link annotations. Email and phone links are not output. |
pages | With outputPages. |
tables | With outputTables: [{ page, columns, rowCount, confidence, rows }]. |
ocrNeededPages | Pages with almost no text (under 20 characters) that contain images: scans. |
truncated | true when pages were left unread: maxPages, the time limit, or your maximum cost per run. |
notes | Plain-language remarks (pages cut, lines removed, scanned pages). |
error, errorCode | Why a row has no content, otherwise null. See below. |
fetchedAt | When the file was fetched. |
The run's key-value store has a SUMMARY record with counts (rows, failures by code, PDFs without text, billed PDFs and extra pages) and request statistics.
Errors are rows, not crashes
error | errorCode | Charged |
|---|---|---|
not a PDF (an HTML page that answers 200, an empty body) | NOT_PDF | no |
HTTP 404 and other HTTP errors, HTTP 403 (access denied), HTTP 429 (rate limited) | HTTP_ERROR, ACCESS_DENIED, RATE_LIMITED | no |
blocked by robots.txt, or robots.txt could not be read | ROBOTS_BLOCKED, ROBOTS_UNREADABLE | no |
file too large (...) | TOO_LARGE | no |
password required, wrong password | PASSWORD_REQUIRED, WRONG_PASSWORD | no |
no text found: the pages are scanned images and need OCR... or no extractable text | NO_TEXT | no |
could not parse PDF: ... | PARSE_FAILED | no |
pageRange selects no pages (the PDF has N) | BAD_PAGE_RANGE | no |
timeout after N s, domain not found, TLS error, connection refused, private or internal address, not allowed, bad redirects | TIMEOUT, DNS, TLS, CONNECTION_REFUSED, PRIVATE_ADDRESS, BAD_REDIRECT, FETCH_FAILED (and INTERNAL for anything unexpected) | no |
invalid URL, unsupported URL: only http and https are read | INVALID_URL | no |
Scanned PDFs
This actor does not do OCR. A page with fewer than 20 characters of text that contains images is listed in ocrNeededPages (and has needsOcr: true in pages[]). A PDF is charged only when at least one page has 20 or more characters of text, so a pure scan is free and a mixed document is charged once, with its scanned pages flagged. Run OCR on the flagged pages with a tool of your choice.
What it does not do
- No OCR (v1). Scanned pages come back empty and flagged, never guessed.
- No form field values, no images, no figures. Only the page text. Text inside charts and figures is read as it is stored and can look odd.
- Tables are a heuristic. Rows of text that line up in 3 or more columns become a table. Simple tables work well (the sample above is 10 columns by 26 rows,
confidence: "high"), complex layouts with merged cells or wrapped cell text may not.confidenceishigh,mediumorlow: low means ragged columns, so check the result. Tables that continue over a page break are returned as one table per page. - Headings come from font size. Lines noticeably larger than the document's body text become headings (levels 1 to 4, largest first). A heading in bold at body size stays a paragraph, and a bold run-in heading is not split from its paragraph.
- Complex layouts can read out of order. Pages with 2 to 4 plain text columns are read column by column; pages that mix boxes, sidebars, forms and columns (a fillable form is the usual example) may come out in a different order than a person would read them.
- Hyphens at line ends. A hyphen at the end of a line followed by a lower-case letter is treated as hyphenation and removed ("interna-" and "tional" become "international"). A real compound broken over two lines ("English-" and "to-German") loses its hyphen. A hyphen followed by a capital or a digit is kept ("COVID-" and "19" become "COVID-19").
- Rotated text. A page drawn sideways is turned upright. Text at other angles (watermarks, side stamps) is left out and noted.
- Fonts without a text mapping. Some PDFs store text in fonts with no Unicode mapping; their text comes out as odd characters or as nothing, exactly as copy and paste from a viewer would.
Pricing
| What | Price |
|---|---|
| One PDF with extracted text (up to 100 pages read) | USD 0.002 (USD 2 per 1,000) |
| Each page read beyond 100 in one PDF | USD 0.0001 |
| Run start | USD 0.001 per run |
| Failed downloads, non-PDF answers, errors, PDFs with no text layer | Free |
Worked examples on the free plan:
- The default input (3 small PDFs): 3 x 0.002 + 0.001 = USD 0.007.
- 1,000 PDFs of 20 pages: USD 2.00 (+ the start fee).
- One 300-page report: 0.002 + 200 x 0.0001 = USD 0.022.
- 10,000 PDFs of which 800 fail or are scans: 9,200 x 0.002 = USD 18.40.
Higher Apify plans get a discount (10% on Silver, 20% on Gold and above). You can set a maximum cost per run: the actor reads no more PDFs than that budget can pay for, reads fewer pages of a long PDF when the extra pages would not fit (the row says truncated: true), stops cleanly and keeps every row already written.
Speed and limits
- Reading time is about 10 milliseconds per page on a laptop and roughly 50 milliseconds per page on the default 1 GB platform container (a 120-page, three-column IRS publication takes about 7 seconds there, a 6-page form well under a second), plus download time. The default three-PDF run finishes in about 4 seconds.
- Requests to one website are at least one second apart, so a list of 1,000 PDFs that are all on the same site takes at least 17 minutes however many you read in parallel. Lists spread over many sites run in parallel.
- Memory: the default is 1 GB. Several large files are held in memory at once, so with a large
maxFileMbfewer PDFs are read in parallel. A row must fit in one Apify dataset item (under 9 MB): a row that would be larger losespages[]first and then has its text cut at a paragraph, with a note.
Schedule it
Save your list as a task and schedule it: new filings, price lists or tender documents on the same URLs are read again on every run. Combine it with Government Tenders Monitor (daily bid alerts with links to documents) or RSS Feed Reader and Monitor (feeds that link to PDF reports): feed the document links into the urls field of this actor.
Related actors
- Government Tenders Monitor: Canada, UK & EU Bid Alerts: New public tenders from official open data in one clean feed: CanadaBuys (federal Canada), UK Find a Tender, EU TED, and US SAM.gov with...
- RSS Feed Reader & Scraper: Atom, JSON Feed & New-Item Monitor: RSS feed reader and scraper.
- Website to Markdown Crawler for RAG: Fast, No Browser: Crawl a site into clean Markdown for RAG and LLMs without a browser.
- News Article Extractor: Text, Date, Image from URLs and Feeds: News article scraper and extractor: turn article URLs, RSS or Atom feeds and news sitemaps into clean rows: title, Markdown and plain...
Rights, robots.txt and privacy
- You are responsible for having the right to process a document. This actor reads files at addresses you supply. It does not crawl, log in, get past paywalls or bot protection, or use proxies.
- robots.txt is respected for the host of every file (cached per site, also after redirects). A site that disallows automated access gets a
blocked by robots.txtrow. If robots.txt cannot be read at all (a server error), the file is skipped with a clear reason instead of being fetched blind. - The User-Agent is
Mozilla/5.0 (compatible; pdf-to-markdown/1.0; +https://apify.com/JTPalms). Requests to one host are at least one second apart, and transient errors are retried twice with back-off. - Private and internal addresses (localhost, 10.x, 192.168.x and similar) are refused.
- Author names are dropped by default. The PDF Author field can be a person's name, so it is output only with
includeAuthor. The actor returns what the document itself contains and nothing else: no enrichment, no lookups. Email and phone links in annotations are not listed inlinks. - A password you give is used to open that file only. It is not written to the dataset, the summary or the log.
- Encrypted files that need a password are read only with the password you provide; the actor never tries to guess or remove passwords.
FAQ
Why was my PDF not charged? Failed downloads, non-PDF answers, files over the size limit, password problems and PDFs without any text layer are free. The row explains why in error.
Can it read scanned PDFs? Not in this version. It tells you which pages need OCR and does not charge for empty text.
How do I get text per page? Set outputPages to true. pages[] has one entry per page read with its text and Markdown.
Can I read only part of a long PDF? Yes: pageRange (for example 1-20) or maxPages. Pages beyond 100 cost USD 0.0001 each, so a range keeps the price down.
A table came out wrong. Check confidence. Table detection is a heuristic; open an issue on the actor page with the PDF URL and the page number.