PDF Text Extractor: tables, OCR, Markdown, JSON per page

Extract source-aware per-page text, conservative tables as CSV/Excel-ready rows, metadata, and OCR for scanned PDF pages with confidence scores. Text spacing and reading order depend on the PDF's embedded text layer. Pay per page, no per-file fee.

Run on Apify Pricing API docs

Categories: AI, DEVELOPER_TOOLS, AUTOMATION. Pay per result, no subscription; Apify's free plan includes $5 of monthly usage.

Run it from the command line

curl -sS -X POST "https://api.apify.com/v2/acts/brenton8907~pdf-text-table-ocr-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startUrls": [{"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"}], "urlField": "url", "outputMode": "pages", "maxPagesPerDocument": 0, "extractText": true, "extractTables": true}'

Run it from Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("brenton8907/pdf-text-table-ocr-extractor").call(run_input={
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "urlField": "url",
  "outputMode": "pages",
  "maxPagesPerDocument": 0,
  "extractText": true,
  "extractTables": true
})
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

Use it as an MCP tool (Claude, Cursor, any MCP client)

The actor is listed in the official MCP registry as io.github.brenton-keller/pdf-text-table-ocr-extractor and served by Apify's hosted MCP server:

{
  "mcpServers": {
    "pdf-text-table-ocr-extractor": {
      "url": "https://mcp.apify.com/?tools=brenton8907/pdf-text-table-ocr-extractor",
      "headers": {
        "Authorization": "Bearer YOUR_APIFY_TOKEN"
      }
    }
  }
}

Pricing (pay per event)

eventwhatpricedetails
page-recordPage processed (text layer)$0.0005One PDF page extracted from its text layer: text, tables, metadata.
ocr-page-recordPage processed (OCR)$0.0060One scanned PDF page rendered and read with OCR (Tesseract), with confidence score.

Documentation

Turn publicly reachable PDF URLs into structured, source-aware data: per-page embedded text, tables as real rows (with page numbers, detected headers and a Markdown rendering), document metadata, and OCR for scanned pages with a confidence score. Batch hundreds of files, or chain the actor after a crawler by pointing it at a dataset of URLs. Pure compute: no target website or proxy dependency.

Why this one and not the others: most PDF actors give you text only, or tables only, or OCR only. This actor does all three in one run, bills per page with no per-file or per-run fee, and ships detected tables in a shape you can open in Excel. Text-layer spacing and reading order remain source-dependent; inspect representative output before building an unattended downstream workflow.

What it does

Who it's for

Input examples

A few PDFs, one row per page (default):

{ "startUrls": [{ "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }, { "url": "https://arxiv.org/pdf/1706.03762" }] }

Whole document as one row (full text + all tables), first 20 pages only:

{ "startUrls": [{ "url": "https://example.com/annual-report.pdf" }], "outputMode": "documents", "maxPagesPerDocument": 20 }

Only the tables, one row per table row, ready for CSV:

{ "startUrls": [{ "url": "https://example.com/price-list.pdf" }], "outputMode": "tableRows" }

Scanned invoices in German and English at 300 DPI:

{ "startUrls": [{ "url": "https://example.com/scan.pdf" }], "ocr": "auto", "ocrLanguages": "deu+eng", "ocrDpi": 300 }

Chain after a crawler: read PDF URLs from its dataset:

{ "inputDatasetId": "aBcDeFgHiJkLmNoPq", "urlField": "url", "outputMode": "documents" }

Pages 2 to 5 of a password-protected file, layout-preserving text, no tables:

{ "startUrls": [{ "url": "https://example.com/locked.pdf" }], "password": "s3cret", "pageRange": "2-5", "keepLayout": true, "extractTables": false }

Output

pages mode (default): one row per page

{
  "source_url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "document_id": "3f1c9a7e2b8d4c05",
  "file_name": "fw9.pdf",
  "file_size_bytes": 171243,
  "title": "Form W-9 (Rev. March 2024)",
  "author": "SE:W:CAR:MP",
  "created_at": "2024-03-06T14:51:09Z",
  "page_count": 6,
  "pages_extracted": 6,
  "page_number": 3,
  "width_pt": 612.0,
  "height_pt": 792.0,
  "text": "Form W-9 (Rev. 3-2024) Page 3\nIF the entity/individual on line 1 is a(n) . . . THEN check the box for . . .\n...",
  "char_count": 7654,
  "word_count": 1284,
  "extraction_method": "text",
  "has_text_layer": true,
  "ocr_confidence": null,
  "table_count": 1,
  "tables": [
    {
      "table_index": 0,
      "page_number": 3,
      "row_count": 4,
      "col_count": 2,
      "header": ["IF the entity/individual on line 1 is a(n) . . .", "THEN check the box for . . ."],
      "rows": [["Corporation", "Corporation."], ["Individual or Sole proprietorship", "Individual/sole proprietor."]],
      "markdown": "| IF the entity/individual on line 1 is a(n) . . . | THEN check the box for . . . |\n| --- | --- |\n| Corporation | Corporation. |",
      "bbox": [36.0, 75.4, 300.2, 210.9],
      "strategy": "lines"
    }
  ],
  "error": null,
  "fetched_at": "2026-09-02T05:30:12Z"
}
Field Description
source_url, final_url The URL you gave, and where it redirected to (if it did)
document_id Stable short hash of the source URL; use it to group pages back into documents
file_name, file_size_bytes From the Content-Disposition header or the URL path
title, author, subject, keywords, creator, producer PDF metadata (null when absent)
created_at, modified_at PDF dates normalized to ISO 8601 UTC
page_count, pages_extracted Pages in the file, and how many this run processed (after pageRange / maxPagesPerDocument)
page_number, width_pt, height_pt, rotation 1-based page number and page geometry in points
text, char_count, word_count Page text
extraction_method text (text layer), ocr (Tesseract), or none (blank page, or OCR disabled on a scanned page)
has_text_layer Whether the page had any text objects at all
ocr_confidence Mean Tesseract word confidence 0 to 100 on OCR pages; null otherwise
ocr_error, text_layer_text Set only when OCR failed on a page, or when OCR replaced a thin text layer (the original is kept here)
tables, table_count Tables found on the page (see the table object above). strategy is lines (ruled) or text (alignment)
error Null on success. Error rows have page_number: null and carry the reason (HTTP status, not a PDF, password, size cap, damaged file)
raw_metadata With includeRaw: the untouched PDF metadata dictionary

documents mode: one row per PDF

Same document fields, plus text (all pages joined), pages (array of {page_number, text, extraction_method, ocr_confidence, char_count, table_count}), tables (all tables, each with its page_number), text_pages, ocr_pages, empty_pages, and truncated (true if the run's charge limit cut the document short).

tableRows mode: one row per table row

source_url, document_id, file_name, title, page_number, table_index, row_index, col_count, header, cells, and record (a {header: cell} object when a header row was detected). Export to CSV or Excel and every table row is one spreadsheet row. In this mode OCR is skipped and only pages that contain tables are billed.

Export any of these as JSON, CSV, Excel or via the API like any Apify dataset.

Pricing

Pay per page processed. No per-run fee, no per-file fee. Failed downloads and error rows are free.

Event Price When
Page processed (text layer) $0.50 per 1,000 pages Text and tables from the PDF text layer
Page processed (OCR) $6.00 per 1,000 pages Page had no text layer and was rendered and read with Tesseract

Examples: a 40-page report costs $0.02. A 6-page form with tables costs $0.003. A 10-page scanned invoice batch of 50 files (500 OCR pages) costs $3.00. A crawler dataset of 1,000 single-page PDF brochures costs $0.50.

Set Maximum total charge on the run to cap spend; the actor stops cleanly at the cap and only pushes the pages it charged for. In tableRows mode, pages without tables are not billed.

Tips

Limits and notes

Use from an AI agent (MCP)

Every Apify actor is available as a tool through the Apify MCP server. Add it to Claude, Cursor or any MCP client and call this actor with the JSON input above. Suggested pattern: start with outputMode: "pages", inspect extraction_method, ocr_confidence and a text sample, then use documents mode after representative source files pass your quality check.

Support

Open an issue on the actor page with the run ID and the PDF URL. If a file parses in your PDF viewer but fails here, attach it to the issue and it will be looked at.