PDF to Text, Markdown & Tables (CSV)
Pricing
from $1.00 / 1,000 pdf processeds
PDF to Text, Markdown & Tables (CSV)
Extract text, Markdown and tables as CSV from PDF URLs, with optional RAG chunks that keep page numbers. $1 per 1,000 PDFs plus $0.20 per 1,000 pages.
Pricing
from $1.00 / 1,000 pdf processeds
Rating
0.0
(0)
Developer
Matias Isnard
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Security
Limited permissionsShare
Extract text, Markdown and every table as a downloadable CSV from PDF URLs or your own files, with optional RAG chunks that keep the first and last page of each chunk. Scanned pages are flagged with needsOcr instead of coming back silently empty. $1 per 1,000 PDFs plus $0.20 per 1,000 pages, and files that fail are free.
Give it PDF URLs, or a key-value store with your own PDF files. For each PDF you get one dataset item with:
- the plain text of the whole file and of each page
- Markdown, with headings guessed from font size and tables as Markdown tables
- every table found, as a CSV file (with a URL you can download without a token) and as JSON rows
- metadata: title, author, subject, creator, producer, created and modified dates, page count
- optional chunks for RAG, with an approximate token count and the first and last page of each chunk
Scanned PDFs (pages that are only images, with no text layer) are flagged with needsOcr: true and the list of those pages, instead of silently returning empty text. The Actor does not run OCR.
A PDF that cannot be downloaded or read gets one item with status: "error" and a readable message, and the run goes on with the next one.
Input
{"pdfUrls": ["https://arxiv.org/pdf/1706.03762","https://www.irs.gov/pub/irs-pdf/fw9.pdf"],"keyValueStoreId": "","outputText": true,"outputMarkdown": true,"outputTables": true,"outputChunks": true,"chunkSize": 500,"chunkOverlap": 50,"maxPagesPerPdf": 0,"password": "","maxFileSizeMb": 50,"downloadTimeoutSecs": 120}
pdfUrls: direct links to PDF files, one per line. Duplicates are processed once. Lines that are not http(s) URLs are skipped with a warning.keyValueStoreId: a key-value store from your account with your own files. See "Your own files" below.outputText(default true):textwith the whole document andpageswith the text of each page.outputMarkdown(default true):markdown, one block per page, each starting with<!-- page N -->.outputTables(default true):tables, and one CSV file per table in the run's key-value store.outputChunks(default false):chunksfor RAG.chunkSize(default 500) andchunkOverlap(default 50): in tokens, counted as characters divided by 4. That is close to what OpenAI and similar tokenizers give for English text, but it is an estimate. Chunks are cut at a space or line break, never inside a word. The overlap is capped at half the chunk size.maxPagesPerPdf(default 0, meaning all): only the first N pages are processed and charged.pageCountstill shows the real number of pages.password: for encrypted PDFs. The same password is tried on every file. It is a secret field, stored encrypted and not shown in the run input.maxFileSizeMb(default 50): bigger files get an error item and are not charged.downloadTimeoutSecs(default 120): maximum time to download one file.
Your own files
- In Apify Console go to Storage > Key-value stores and create a store (or use an existing one).
- Upload your PDFs as records. Give each key a
.pdfending, for exampleinvoice-2026-01.pdf, or set the content type toapplication/pdf. From the command line:apify key-value-stores set-value <storeId> report.pdf --content-type application/pdf < report.pdf. With the API:PUT https://api.apify.com/v2/key-value-stores/<storeId>/records/report.pdfwith headerContent-Type: application/pdfand the file as the body. - In the Actor input, pick the store in "Key-value store with your PDFs".
Every .pdf record of the store is processed, together with the URLs in pdfUrls. The Actor gets read access to that store only, nothing else in your account. The url of these items is the record's API URL.
You can also put a record URL like https://api.apify.com/v2/key-value-stores/<storeId>/records/<key> in pdfUrls. That works when the store is picked in the field above, or when the store's General access is set to "Anyone with link can read".
Output
One dataset item per PDF. A real example from a test run, shortened (more in ejemplos/salida.json in the source):
{"url": "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf","status": "ok","processedAt": "2026-10-30T04:21:18+00:00","fileSizeBytes": 43627,"pageCount": 1,"pagesProcessed": 1,"needsOcr": false,"pagesNeedingOcr": [],"metadata": {"title": "PDF Invoice Example","author": "Sliced Invoices","subject": "Example PDF invoice that was created using the Sli","creator": "Sliced Invoices","producer": "Mac OS X 10.10.4 Quartz PDFContext","created": "2016-02-04T00:05:02+00:00","modified": "2016-02-04T00:05:02+00:00"},"error": null,"text": "Invoice\nInvoice Number INV-3337\nFrom:\nDEMO - Sliced Invoices Order Number 12345\n...","pages": [{ "page": 1, "text": "Invoice\nInvoice Number INV-3337\n...", "chars": 567 }],"markdown": "<!-- page 1 -->\n\n# Invoice\n\nPayment is due within 30 days from date of invoice. ...","tables": [{"page": 1,"index": 2,"rowCount": 5,"columnCount": 2,"csvKey": "table-006-p1-2.csv","csvUrl": "https://api.apify.com/v2/key-value-stores/6bgsR9UkDRGs36GCl/records/table-006-p1-2.csv?signature=8Lcot9TKBOXSWvTXihSb","rows": [["Invoice Number", "INV-3337"], ["Order Number", "12345"], ["Invoice Date", "January 25, 2016"], ["Due Date", "January 31, 2016"], ["Total Due", "$93.50"]]}],"chunks": [{ "index": 0, "text": "Invoice\nInvoice Number INV-3337\n...", "pageStart": 1, "pageEnd": 1, "approxTokens": 170 }]}
status:okorerror. On error onlyurl,status,processedAtanderrorare set, for example"HTTP 404 Not Found","Got an HTML page (content type text/html), not a PDF","PDF is password protected, set the password input"or"File is larger than the 50 MB limit".needsOcr:truewhen at least half of the processed pages are images without a text layer.pagesNeedingOcrlists those pages, also when the rest of the file has text. Send those files to an OCR tool.metadata: dates are converted to ISO 8601. Fields the file does not have arenull.tables[].csvUrl: a signed link that works without an Apify token. The CSV files are in the run's default key-value store, namedtable-<file number>-p<page>-<table number>.csv, and they are kept as long as that store (the data retention of your plan).pageErrors: only present when some pages could not be read. The other pages are still returned.fullResultUrl: only present when the item would be bigger than 8 MB (Apify's limit for one dataset item is 9 MB). Thentext,pages,markdown,chunksand the table rows are left out of the dataset and the full item is saved as JSON in the key-value store, at this link.
The "Overview" view of the dataset shows URL, status, pages, needs OCR, title, author, created date and error.
How it works
Each file is downloaded once with a plain GET, with the size limit and timeout from the input. The file must start with %PDF-, otherwise it is reported as not a PDF. Requests identify themselves with the User-Agent Mozilla/5.0 (compatible; ApifyPdfExtractor/1.0; +https://apify.com/isnard/pdf-text-tables-extractor). Files are processed one after the other.
Text and tables are extracted with pdfplumber, which is built on pdfminer.six. Both are MIT licensed. PyMuPDF is faster and its Markdown is better, but it is AGPL, which would require publishing the source of a hosted service built on it, so it is not used.
- A page counts as having no text layer when it has fewer than 20 non-space characters and contains at least one image.
- Tables are found from the lines and cell borders drawn in the PDF. A table needs at least two rows and two columns.
- In the Markdown, text inside a table is written only once, as the table. A line is a heading when its font is at least 1.25 times the size of the page's body text (
##) or at least 1.6 times (#).
Pricing
Pay per event:
pdf-processed: each PDF that was downloaded and read, when at least one page could be read.page-processed: each page extracted, up tomaxPagesPerPdf. Pages listed inpageErrorsare not charged.
Error items (404, not a PDF, too big, wrong password, no readable page, private or local address) are not charged. You can cap the cost of a run with the "Maximum cost per run" setting. When the cap is reached the Actor stops after the current file.
Limitations
- No OCR. Scanned pages are flagged, not read.
- Tables without drawn borders (columns aligned only by spaces) are usually not detected as tables. Their text is still in
textandmarkdown. - Layout boxes, for example the frame around an invoice, can come out as a table, and a table inside another one appears twice.
- Multi-column layouts can mix columns in
text. Rotated text and some form fields come out in odd order or reversed. - Some PDFs space out every letter (
0 3 / 2 5), and the text comes out like that. - Tokens are an estimate (characters divided by 4), not a real tokenizer.
- Only files reachable with a plain GET. No cookies, login or custom headers. Sites that block automated requests answer 403, which is reported as an error.
- Very large or complex files (thousands of pages, heavy vector drawings) are slow, around 1 to 2 seconds per page in the worst case seen in testing. Use
maxPagesPerPdfto limit them. Each PDF gets at most 300 seconds: pages after that are left out, not charged, and the item gets awarning.
Run locally
python -m venv .venv.venv/Scripts/pip install -r requirements.txt # on Linux/macOS: .venv/bin/pip# put input in storage/key_value_stores/default/INPUT.json.venv/Scripts/python -m my_actorpython -m unittest discover -s tests -t .
The test fixtures in tests/fixtures are written by tests/make_fixtures.py without any PDF library.
Author: Matias Isnard.
FAQ
Does it read scanned PDFs?
No OCR. Scanned pages are listed in pagesNeedingOcr so you can send them to an OCR tool.
Which tables are detected? Tables with drawn lines or cell borders. Columns aligned only with spaces come out as text.
What if a PDF fails? A 404, a non-PDF, an oversized file or a wrong password gives an error item that is not charged.
Size limits?
50 MB per file by default (maxFileSizeMb). maxPagesPerPdf caps pages processed and charged.
Related tools
- OCR: Image & Scanned PDF to Text (Spanish, English, Portuguese): read the scanned pages this Actor lists in pagesNeedingOcr.
- Bulk Article Extractor: Clean Text & Markdown for LLMs: extract text from web pages.