PDF to Markdown, Tables & OCR for RAG avatar

PDF to Markdown, Tables & OCR for RAG

Pricing

Pay per event

Go to Apify Store
PDF to Markdown, Tables & OCR for RAG

PDF to Markdown, Tables & OCR for RAG

Turn PDFs into clean Markdown, plain text, per-table JSON and RAG chunks with page numbers. OCR runs only on scanned pages. Public URLs or Key-value store files. Pay per page, no subscription.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Norton Claude

Norton Claude

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

20 days ago

Last modified

Security

Limited permissions

Share

Turn PDF files into clean Markdown, plain text, tables as JSON, and RAG-ready chunks with page numbers. Paste a link, press Start, get text you can feed to an LLM, a vector database or a spreadsheet.

The Actor reads the text that is already inside the PDF (fast, cheap) and only runs OCR on the pages that really are scans. A report with 40 text pages and 2 scanned appendix pages pays the OCR price for 2 pages, not 42.

What you get

  • Markdown with headings, paragraphs, lists and GitHub tables, in reading order (columns are not interleaved), with running headers, footers and page numbers removed.
  • Plain text of the same content, if you prefer it.
  • Every table as JSON next to the Markdown: rows (first row is the header), records (one object per row when the header is clean), and the table as a Markdown string, each with its page number.
  • RAG chunks: pieces of text of the size you choose, each with pageStart, pageEnd, pages[], the heading it sits under and a stable chunkId, so every answer can cite "page 12".
  • OCR only where needed (scans, photographed documents, PDFs with a broken text layer). The page is marked source: "ocr" and you get a confidence score.
  • Scanned and text pages mixed in one file are handled page by page.
  • Passwords for encrypted PDFs (kept secret, never logged or stored in the output).
  • One bad file never stops the run. A wrong link, a non-PDF, a locked file or a corrupt PDF becomes one dataset item that says why, and costs nothing.
  • Private files work with a signed link: upload the PDF to your own Apify Key-value store and paste the record's public link (it carries a signature, so it opens without logging in).

Who uses it

  • Teams building RAG and AI agents over contracts, reports, manuals, municipal and government documents.
  • Analysts who need the tables out of PDF reports (budgets, statistical releases, annual reports) as rows and columns.
  • Back-office automation that turns scanned invoices and forms into searchable text.
  • Anyone who needs a clean Markdown copy of a paper or a report to read, search or summarise.

How to use it

  1. Paste one or more PDF URLs (the link must lead straight to the file). The prefilled example works as is.
  2. Pick the output shape: one item per PDF, one item per page, or RAG chunks.
  3. Press Start. Download the dataset as JSON, CSV, Excel or HTML, or read it through the API.

Leave everything else on its defaults; the defaults are what most people want.

Example inputs

Read a report, get one item with the Markdown and all tables:

{
"pdfUrls": [{ "url": "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf" }]
}

Chunks for a vector database, 1,200 characters with 150 overlap, only pages 1 to 20:

{
"pdfUrls": [{ "url": "https://example.com/handbook.pdf" }],
"outputMode": "chunks",
"chunkSize": 1200,
"chunkOverlap": 150,
"pageRange": "1-20"
}

An encrypted PDF, and a file from your own Key-value store (use the record's public link, which includes ?signature=...):

{
"pdfUrls": [
{ "url": "https://example.com/locked-statement.pdf" },
{ "url": "https://api.apify.com/v2/key-value-stores/YOUR_STORE_ID/records/scanned-invoice.pdf?signature=YOUR_SIGNATURE" }
],
"password": "the-pdf-password"
}

The same password is tried on every PDF in the run.

Main options

OptionWhat it does
pdfUrlsLinks to PDF files (http or https). One per line, or paste a list.
outputModedocument (one item per PDF), pages (one per page) or chunks (RAG pieces).
pageRange1-10,15, 20- (page 20 to the end), -5 (first five). You pay only for pages read.
extractTablesDetect tables (ruled or aligned) and return them as Markdown tables and JSON. On by default.
ocrauto (scanned pages only), off, or force (every page).
includeMarkdown, includeTextWhich text formats to return.
chunkSize, chunkOverlapChunk length and overlap, in characters (about 4 characters per English token).
removeHeadersFootersDrop lines repeated at the top or bottom of most pages.
maxPagesPerDocumentCut very long PDFs (default 500) and mark them partial.
maxFileSizeMbRefuse bigger files before download completes (default 50, not charged).
passwordFor encrypted PDFs. Secret.

Output

One dataset item per PDF in the default mode. Real output from a Statistics South Africa release (shortened; the full item also has the whole markdown, 12 tables and 20 pages):

{
"recordType": "document",
"url": "https://www.statssa.gov.za/publications/P0441/P04412ndQuarter2024.pdf",
"documentId": "25e71ceff235",
"title": "Gross domestic product",
"pageCount": 20,
"status": "ok",
"error": null,
"metadata": { "author": "mariette", "creator": "Microsoft® Word 2016", "createdAt": "2024-09-02T12:28:17+02:00" },
"pagesRequested": 20,
"pagesProcessed": 20,
"textPages": 20,
"ocrPages": 0,
"blankPages": 0,
"tableCount": 12,
"wordCount": 9773,
"markdown": "<!-- page 1 -->\n\n## STATISTICAL RELEASE\n\n### P0441\n\n# Gross domestic product\n\n### Second quarter 2024\n\n...\n\n<!-- page 3 -->\n\n## Key findings for the second quarter of 2024\n\nReal gross domestic product (GDP) measured by production, increased by 0,4%¹ in the second quarter of 2024 ...",
"tables": [
{
"id": "p19-t1",
"page": 19,
"index": 1,
"kind": "ruled",
"rowCount": 9,
"columnCount": 3,
"rows": [
["Statistical release", "Reason for revision", "Period subject to revision"],
["Second quarter 2024", "Updated source data", "First quarter 2024"],
["Third quarter 2024", "Updated source data", "Second quarter 2024"]
],
"records": [
{ "Statistical release": "Second quarter 2024", "Reason for revision": "Updated source data", "Period subject to revision": "First quarter 2024" }
],
"markdown": "| Statistical release | Reason for revision | Period subject to revision |\n| --- | --- | --- |\n| Second quarter 2024 | Updated source data | First quarter 2024 |\n..."
}
],
"warnings": [],
"charges": { "document": 1, "page": 20, "ocrPage": 0, "totalUsd": 0.013 },
"fileSizeBytes": 1143899,
"sha256": "cf6b6eec89d6ae4b08f7a4a24e82c4c93c3fc0496ed29db22649170af13d081b",
"processingSeconds": 6.5,
"extractedAt": "2026-10-17T12:11:28+00:00"
}

tables[].kind is ruled (drawn lines) or aligned (columns found by position). Each table's markdown is also placed in the document Markdown at the place it appears.

Chunk mode

Every chunk knows where it came from (real output, one chunk):

{
"recordType": "chunk",
"url": "https://www.statssa.gov.za/publications/P0441/P04412ndQuarter2024.pdf",
"documentId": "25e71ceff235",
"chunkId": "25e71ceff235-0001",
"chunkIndex": 1,
"chunkCount": 5,
"text": "contributed 0,3 of a percentage point to the GDP growth.\n\nThe trade industry increased by 1,2% and contributed 0,1 of a percentage point. ...",
"pageStart": 3,
"pageEnd": 3,
"pages": [3],
"heading": "Key findings for the second quarter of 2024",
"headingPath": ["Key findings for the second quarter of 2024"],
"charCount": 412,
"usesOcr": false
}

Chunks follow paragraph, list and table boundaries where they can. A table is never cut in the middle of a row when it fits in a chunk. chunkOverlap repeats the end of one chunk at the start of the next.

Page mode

One item per page with page, source (text, ocr or blank), markdown/text, that page's tables, ocrConfidence, width, height and warnings.

Run summary

The key-value store record SUMMARY lists every PDF with its status, page counts, tables, seconds and cost, plus totals and peakMemoryMb. When a run stops at your cost limit it says so in stoppedBecause.

Statuses and error codes

status is ok, partial (some pages missing, for example the page limit or your cost limit was reached), empty (nothing readable, for example a scan with OCR off), failed or skipped (not started because the cost limit was reached).

error.codeMeaning
INVALID_URL, BLOCKED_URLNot an http(s) link, or it points at a private or internal address (refused on purpose).
DNS_FAILED, HTTP_404, HTTP_403, HTTP_xxx, TIMEOUT, TLS_ERROR, TOO_MANY_REDIRECTS, DOWNLOAD_FAILEDThe download failed. retryable tells you whether trying later may help.
NOT_A_PDFThe link or record is not a PDF (an HTML page, JSON, an image).
FILE_TOO_LARGEOver maxFileSizeMb.
PASSWORD_REQUIRED, WRONG_PASSWORDEncrypted PDF without or with the wrong password.
CORRUPT_PDF, EXTRACTION_FAILEDThe file cannot be parsed, or no page could be read.
RECORD_NOT_FOUND, STORE_ERRORThe Key-value store record does not exist or cannot be read.
CHARGE_LIMIT_REACHEDStatus skipped: your maximum cost per run was reached first.
INTERNAL_ERRORSomething unexpected went wrong with this one PDF; the others are not affected.

A page that could not be read inside an otherwise good PDF is listed in failedPages with a code (PAGE_TIMEOUT after 90 s, or 180 s when OCR may run; MEMORY_LIMIT, WORKER_CRASHED, EXTRACT_FAILED). Such pages are not charged.

Pricing

Pay per use, no subscription, no start fee. Three events:

EventPriceWhen
PDF processed$0.003Once per PDF that was read successfully.
Page read from the text layer$0.0005Per page whose text was read directly.
Page read with OCR$0.004Per scanned page that OCR actually read.

Failed PDFs (404, not a PDF, wrong password, corrupt file) cost nothing. Blank pages cost nothing. A page where OCR finds no text costs nothing.

Worked examples (from real runs):

PDFPagesCost
Mozilla pdf.js sample paper (the prefilled example)14 text0.003 + 14 x 0.0005 = $0.010
Statistics South Africa GDP release20 text0.003 + 20 x 0.0005 = $0.013
A 40-page generated report with a table on every page40 text0.003 + 40 x 0.0005 = $0.023
Scanned invoice batch file3 scans0.003 + 3 x 0.004 = $0.015
Report with 2 text pages and 1 scanned page2 text, 1 scan0.003 + 2 x 0.0005 + 0.004 = $0.008
1,000 PDFs of 10 text pages each10,000 text1,000 x (0.003 + 10 x 0.0005) = $8.00

Set Maximum cost per run in the run options to cap what you spend. The Actor stops cleanly at the limit: PDFs it did not reach are listed as skipped, and a PDF that hits the limit halfway is returned as partial with the pages already read. It never goes over the limit. With OCR on auto, a page is only attempted with OCR when the OCR price still fits in your remaining budget.

Limits (read this before you rely on it)

  • OCR reads printed text only: English and Chinese letters, digits and common punctuation. No handwriting. No Cyrillic, Greek, Arabic, Hebrew or Indic scripts. Accented Latin letters are often returned without the accent (e for é). Similar shapes get confused now and then (0 and o).
  • OCR word breaks are rebuilt from the page image, because the OCR model drops spaces. On clean A4 scans at 150 dpi we measured about 98% of words exactly right and 99%+ character accuracy; typewriter fonts, tiny print and poor scans are worse. Always check numbers that matter.
  • Scanned tables come out as lines of text, not as table cells. Table structure is recovered from the text layer only.
  • Complex tables are not perfect. Ruled and clearly aligned tables work well. Merged cells, tables with multi-line wrapped cells, nested headers and tables that continue over several pages can come out fragmented (a wrapped cell may become two rows; page-spanning tables are not joined). Check important tables.
  • Layout is a best effort. Multi-column text is read column by column, but unusual layouts (magazines, posters, forms with boxes) can come out in the wrong order. Text inside figures, charts and code listings can leak into the running text as noise. On pages where most text is rotated or mirrored (some plots and attention maps) tables are not extracted and a warning is added.
  • Not extracted: images and their content, form fields, annotations, hyperlinks, vector drawings, PDF bookmarks. Footnote numbers are kept as superscript characters next to the word.
  • Public links only for downloads. Some sites block automated downloads (bot protection, login walls); when a site answers 403 the Actor retries with a browser-style identity, but it cannot get past a login or a bot challenge. Upload such files to your own Key-value store and paste the record's public link instead.
  • Safety refusals: links to private networks, localhost and cloud metadata addresses are refused, also after redirects. Only ports 80, 443, 8080 and 8443 are allowed.
  • Size limits: 50 MB per file by default (up to 200 MB), 500 pages per file by default (up to 5,000).
  • Memory: text-only runs need about 200 MB. Scanned pages need about 700 MB, so use at least 1024 MB (the default) for scans and 2048 MB or more for large scans or long OCR batches. If a page does not fit, that page fails with a clear message and is not charged.
  • Speed: text pages take about 0.2 to 0.4 s each. OCR takes about 3 s per A4 page on a fast core. Apify gives more CPU to bigger memory settings, so give scan-heavy runs 2048 to 4096 MB if you need them quickly.
  • requestsFromUrl lists (a link to a text file of links) are not supported; paste the links.

FAQ

Does it work on scanned PDFs? Yes. Leave OCR on auto. Pages without a usable text layer (or with a garbled one) are OCR'd, the rest are read directly. Switch to force only if you know the text layer of a file is wrong in a way the Actor cannot detect.

How do I know which pages were OCR'd? Each page record and each chunk says so (source, usesOcr), and the document item has ocrPages and ocrConfidence.

Why is title sometimes the file name? The title comes from the PDF's own metadata when it looks like a real title, otherwise from the first large heading on the first two pages that were read, otherwise from the file name.

What happens to my files? Downloaded files are processed in a temporary folder inside the run and deleted when each document is done. The Actor does not keep your PDFs, does not send them to any other service, and the PDF parser runs in a separate process with no access to your API token.

Can I read a PDF from Google Drive, Dropbox or S3? Use a direct download link, or a presigned URL. Links that open a viewer page are not PDFs and return NOT_A_PDF. Signed URLs are never written to the log (query strings are removed).

What are the characters in chunk size? Characters, not tokens. As a rule 1,500 characters is roughly 375 English tokens.

Can I run it for thousands of PDFs? Yes; one run can take thousands of links. Failures are isolated per document. A run that the platform restarts is designed to continue where it stopped and not to charge the same PDF twice.

Use with RAG and vector stores

Use outputMode: "chunks". Every item is ready to embed, with the metadata most vector stores want:

import os, requests
url = "https://api.apify.com/v2/acts/mzansi-dataworks~pdf-to-markdown-tables-ocr/run-sync-get-dataset-items"
chunks = requests.post(
url,
params={"token": os.environ["APIFY_TOKEN"]},
json={"pdfUrls": [{"url": "https://example.com/handbook.pdf"}], "outputMode": "chunks", "chunkSize": 1200, "chunkOverlap": 150},
timeout=600,
).json()
for c in chunks:
if c["recordType"] != "chunk": # failed or skipped PDFs come back as one "document" item that says why
print("not read:", c["url"], c["error"]["code"])
continue
store.add( # your vector store
id=c["chunkId"],
text=c["text"],
metadata={"source": c["url"], "title": c["title"], "pages": c["pages"], "heading": c["heading"]},
)

Because chunkId is stable for the same file and settings, re-running a PDF lets you upsert instead of duplicating. Answers can cite source and pages.

For LangChain, LlamaIndex or Haystack, load the dataset items with the Apify integration of your framework and map text to the page content and the other fields to metadata.

Use with AI agents and the Apify MCP server

The Actor is a normal Apify Actor, so any agent that speaks to the Apify MCP server can call it as a tool: add mzansi-dataworks/pdf-to-markdown-tables-ocr to the tools of your MCP client (or let it discover Actors by search) and ask for "the text of this PDF" or "the tables on pages 4 to 6". Two tips for agents:

  • Use pageRange and outputMode: "pages" to read only what the question needs; you pay per page.
  • Read status, warnings and error.code from the result: a partial or empty item tells the agent what to change (raise the cost limit, switch OCR on, give a password) instead of silently returning nothing.

Privacy and safety

  • Only http and https links are fetched, with limits on redirects, time and size. Private, loopback, link-local and cloud-metadata addresses are refused, and checked again after every redirect.
  • PDFs are parsed in a separate worker process with no API tokens in its environment. Nothing inside a PDF is ever executed (no JavaScript, no launch actions).
  • The password you give is a secret input; it is never written to the log or to the dataset.
  • The Actor outputs what is in the PDFs you give it. If those contain personal data, you are responsible for how you use the output.