# PDF Text Extractor with OCR, Tables & Markdown (`usefulapi/pdf-text-ocr`) Actor

Extract text, tables and metadata from any PDF, digital or scanned. Built-in OCR in 124 languages reads scanned pages automatically. Get Markdown for AI and RAG, tables as CSV, filled form fields and per-page results. USD 0.30 per 1,000 text pages, USD 3 per 1,000 OCR pages.

- **URL**: https://apify.com/usefulapi/pdf-text-ocr.md
- **Developed by:** [Useful API](https://apify.com/usefulapi) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 text pages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## PDF Text Extractor with OCR, Tables & Markdown

Get the text out of any PDF: born-digital or scanned, one file or ten thousand. Text pages are read from the PDF's
own text layer; scanned pages are read with OCR in 124 languages, automatically. Tables come out as rows and CSV,
and every document also comes as Markdown, ready for AI and RAG pipelines.

Images work too (PNG, JPEG, TIFF including multi-page, WebP, GIF, BMP).

### Why this one

- **Scans and text PDFs in one run.** Each page is checked: pages with a text layer are read directly (fast, exact),
  pages without one are read with OCR. Mixed files (a typed contract with a scanned signature page) just work.
- **OCR at USD 3 per 1,000 pages.** Text pages cost USD 0.30 per 1,000. You pay OCR only for the pages that need it.
- **Tables as data.** Tables with drawn cell borders come out as rows, as CSV and as Markdown tables, with their page
  number and position. Tables ruled only every few rows are split back into one row per line.
- **Filled-in forms.** The values typed into fillable PDF forms come out as `formFields` (name, type, value).
- **Markdown for AI.** Headings from font sizes, lists, paragraphs joined back together, tables in place.
- **Hard scans handled.** A page scanned sideways or upside down is turned and read again; old book scans with dark
  edges or grey paper are read with a second, local thresholding pass.
- **Dead links and failed pages are free.** A link that does not answer, or a page the engine cannot read, is not
  billed (as long as it did not hold the run for long). Blank pages are billed as cheap text pages, not OCR pages.
  Every file and every page says what happened in plain words.
- **One bad file never stops the run.** Each file is read in a separate process. A broken, encrypted or huge file
  gets a clear error in its own row, and the run goes on with the next one.
- **Big files.** Up to 5,000 pages and 500 MB per file (long scans need a longer run timeout, see Good to know).
- **Private.** Documents are read inside the run with open-source software (PDFium, pdfplumber, Tesseract). Nothing
  is sent to any OCR or AI service.

### What you get

One row per file (or one row per page, if you prefer):

| Field | What it is |
|---|---|
| `fileName`, `url`, `fileType`, `fileSizeBytes`, `sha256` | The file |
| `status` | `ok`, `partial` (some pages failed, or reading stopped early: budget, timeout; the reason is in `error`) or `error` |
| `error`, `errorCode` | What went wrong, in plain words (`password-required`, `http-error`, `file-too-large`...) |
| `pageCount`, `pagesRead`, `pagesOcr`, `pagesNotRead`, `tableCount` | Counts (`pagesNotRead` includes pages left out on purpose by "Pages to read" or "Max pages per file") |
| `title`, `author`, `createdAt`, `modifiedAt` | From the PDF's metadata |
| `text` | All text, in reading order |
| `markdown` | The same as Markdown (headings, lists, tables) |
| `tables` | Every table: `page`, `rows` (a list of rows of cells), `csv`, `rowCount`, `columnCount`, `bbox` |
| `formFields` | Filled-in form fields: `page`, `name`, `type`, `value` (and `checked` for ticked boxes) |
| `pages` | Per page: `text`, `source` (`text`, `ocr`, `blank`, `empty` or `error`), `ocrConfidence`, `ocrRotated`, `ocrSeconds`, size, notes |
| `notes` | Anything to know about the file (for example an XFA form) |
| `metadata` | Producer, creator, PDF version, encryption, tagged PDF, form type, attachments, outline (bookmarks) |
| `billed` | The events this file was billed for |

Example: the default input (the IRS Form W-9 PDF), shortened:

```json
{
  "fileName": "fw9.pdf",
  "status": "ok",
  "pageCount": 6,
  "pagesRead": 6,
  "pagesOcr": 0,
  "tableCount": 2,
  "title": "Form W-9 (Rev. March 2024)",
  "createdAt": "2024-03-06T08:18:13-05:00",
  "text": "Form W-9\n(Rev. March 2024)\nRequest for Taxpayer\nIdentification Number and Certification\n...",
  "tables": [
    { "page": 3, "rowCount": 6, "columnCount": 2,
      "rows": [["IF the entity/individual on line 1 is a(n) . . .", "THEN check the box for . . ."],
               ["• Corporation", "Corporation."]] }
  ],
  "pages": [{ "page": 1, "source": "text", "text": "Form W-9\n(Rev. March 2024)\n..." }],
  "billed": { "document": 1, "page": 6 }
}
```

The run's `OUTPUT` record sums it up: files read, pages read with and without OCR, errors and events billed.

A document too large for one dataset row (about 9 MB of results, roughly 800 text pages or 300 pages full of tables)
comes as one summary row followed by one row per page.

### How to use it

Paste PDF or image links, upload a text file of links, or upload the files themselves. Google Drive and Dropbox share
links work when the file is shared publicly. For a file already in your own Apify storage, use its shareable link
(the record URL with `?signature=...`): the Actor runs with limited permissions and cannot open your other storages
with a plain link.

**Read a batch of PDFs, scans included:**

```json
{ "startUrls": [{ "url": "https://example.com/report.pdf" }, { "url": "https://example.com/scan.pdf" }] }
```

**Scanned documents in French and German, one row per page:**

```json
{ "startUrls": [{ "url": "https://example.com/scan.pdf" }], "ocrLanguages": ["fra", "deu"], "outputMode": "page" }
```

**Only the first two pages of each file** (cheap triage of a big archive):

```json
{ "startUrls": [{ "url": "https://example.com/a.pdf" }], "pageRange": "1-2" }
```

**Also look for tables lined up by spaces only** (experimental: finds some tables the default misses, and skips
anything that looks like a paragraph):

```json
{ "startUrls": [{ "url": "https://example.com/report.pdf" }], "tables": "text" }
```

**OCR every page** (for text inside pictures, or PDFs with a broken text layer; the exact text layer is then replaced
by OCR text and every page is billed as an OCR page):

```json
{ "startUrls": [{ "url": "https://example.com/brochure.pdf" }], "ocrMode": "always" }
```

#### Input options

| Option | Default | What it does |
|---|---|---|
| PDF or image URLs | | Links to files (or upload a list) |
| Upload files | | Files from your computer |
| OCR mode | Auto | Auto: OCR only pages without text. Always: every page. Never: no OCR |
| OCR languages | English | Up to 3 of 124 languages; the first is the main one |
| OCR resolution | 300 DPI | 400 for very small print (US Legal pages then count as 2 OCR pages), 200 for large print |
| Tables | Ruled tables | Tables with drawn borders; also tables aligned by spaces (experimental); or off |
| Markdown | On | Also return Markdown |
| Text with the page layout kept | Off | Text placed as on the page (columns side by side) |
| One row per | Document | Or one row per page |
| Pages to read | All | For example `1-5, 8, 10-` |
| Max pages per file | 1,000 | Up to 5,000 |
| Max file size | 100 MB | Up to 500 MB |
| PDF password | | For password-protected PDFs |

### Pricing

Pay per event, no subscription:

| Event | Price | Per 1,000 |
|---|---|---|
| Run start | USD 0.00075 per GB of memory: **USD 0.003 per run** (it always runs at 4 GB) | |
| File read (download, open, metadata; also a file that turns out locked or broken) | USD 0.001 | USD 1 per 1,000 files |
| Page read from the text layer (text, tables, Markdown) | USD 0.0003 | **USD 0.30 per 1,000 pages** |
| Page or image read with OCR | USD 0.003 | **USD 3 per 1,000 pages** |
| Each MB of a file past the first 2 MB | USD 0.0004 | |

Two things make an OCR page count as more than one, because they take longer to read:

- Chinese, Japanese and Korean: x2.
- Very large pages (over 16 megapixels as rendered: A3, newspaper and drawing scans, or US Legal at 400 DPI; A4, US
  Letter and Legal at 300 DPI and A4 or Letter at 400 DPI stay at 1): x2 up to 32 megapixels, x3 above.

Each file's `billed` field and the run's summary show exactly what was counted.

Examples:

- One 10-page text PDF in its own run: 0.003 + 0.001 + 10 x 0.0003 = **USD 0.007**
- 1,000 ten-page text PDFs in one run (10,000 pages): 0.003 + 1,000 x 0.001 + 10,000 x 0.0003 = **USD 4.00**
- One 200-page scanned book in English, 30 MB: 0.003 + 0.001 + 200 x 0.003 + 28 x 0.0004 = **USD 0.62**
- Links that do not answer, pages that fail: **free** (as long as they do not hold the run for long)

Set "Maximum cost per run" on the run and it stops cleanly just before that amount, keeping everything read so far:
a file cut short gets a `partial` row with the pages it read.

### Good to know

- **Accuracy.** Text-layer pages are exact (what the PDF contains). OCR is Tesseract 5 (LSTM models): very good on
  clean print, weaker on handwriting, low-quality photos and decorative fonts. Each OCR page has a confidence score
  (0 to 100) so you can flag pages to check.
- **Reading order** follows the PDF's own order, which is right for papers, reports and most documents. For forms and
  tables, switch on "Text with the page layout kept" (on very tightly set text it can run words together).
- **Tables** are found on pages with a text layer and drawn cell borders. Tables without borders, tables on rotated
  pages and tables on scanned pages come out as text, not as rows.
- **Which pages get OCR (auto mode):** pages with (almost) no text layer and a picture on them, pages whose text layer
  is unreadable, and pages that are one full-page picture with only a stamp or header on top (fewer than 200
  characters).
- **Vertical Chinese, Japanese or Korean** (old books and newspapers): pick the "vertical" language models
  (`chi_tra_vert`, `jpn_vert`...); the normal models read vertical print poorly and slowly.
- **Not read:** handwriting recognition is not offered; XFA forms show only what PDF readers without a form engine
  show (often a "Please wait" page; filled-in values still come out in `formFields`); encrypted PDFs need their
  password.
- **Long runs:** the run stops starting new pages shortly before its timeout and saves what it read (the file in
  progress gets a `partial` row). For long scans, raise the run timeout.
- **Limits per page:** each page has a time limit. A very dense or noisy page (old newspapers, complex drawings)
  can reach it: OCR then returns the text read so far with `ocrTimedOut: true`, and a text page that runs over is
  skipped with a note, instead of holding the whole run.
- **Low-resolution scans** are read at up to twice their own resolution (never blown up to 300 DPI), which is faster
  and reads just as well.

### Use it from code

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("usefulapi/pdf-text-ocr").call(run_input={
    "startUrls": [{"url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"}],
})
for doc in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(doc["fileName"], doc["status"], len(doc.get("text") or ""))
```

### Licences

Built on open-source software: PDFium through pypdfium2 (BSD-3-Clause / Apache-2.0), pdfplumber and pdfminer.six
(MIT), Tesseract 5 (Apache-2.0) through tesserocr (MIT), Tesseract language models from tessdata_fast (Apache-2.0),
Pillow (MIT-CMU), httpx (BSD-3-Clause).

### Questions and problems

Open an issue in the Issues tab with the link to the file (if it is public) and what you expected.

# Actor input Schema

## `startUrls` (type: `array`):

Direct links to PDF files or images (PNG, JPEG, TIFF, WebP, GIF, BMP). Google Drive and Dropbox share links work when the file is public. You can also give a text file of links (one per line).

## `files` (type: `array`):

Upload PDFs or images from your computer (they go to a key-value store on your account).

## `ocrMode` (type: `string`):

Auto reads scanned pages with OCR and every other page from its exact text layer. Always reads every page with OCR (also text in pictures; every page is then billed as an OCR page). Never skips OCR. Images are always read with OCR.

## `ocrLanguages` (type: `array`):

Languages printed on the scans, up to 3 (first = main language). 124 languages; pick only the ones you need, more languages make OCR slower and less accurate. Chinese, Japanese and Korean pages count as 2 OCR pages; for vertical print pick the "vertical" models.

## `ocrDpi` (type: `integer`):

300 suits normal print. Use 400 for very small print (US Legal pages then count as 2 OCR pages); 200 for large print (faster).

## `tables` (type: `string`):

Ruled tables finds tables with drawn cell borders. Ruled and aligned (experimental) also looks for tables lined up by spaces only; it skips anything that looks like a paragraph but can still miss or split such tables.

## `markdown` (type: `boolean`):

Also return Markdown: headings from font sizes, lists, tables as Markdown tables. Good for AI and RAG pipelines.

## `layoutText` (type: `boolean`):

Also return text placed as on the page (columns side by side, padded with spaces). Useful for forms and reports.

## `outputMode` (type: `string`):

Document: one row per file with all its pages. Page: one row per page.

## `pageRange` (type: `string`):

For example "1-5, 8, 10-". Empty = every page.

## `maxPagesPerFile` (type: `integer`):

Pages read per file at most (after "Pages to read"). Up to 5,000.

## `maxFileSizeMb` (type: `integer`):

Larger files are skipped with a clear message. Up to 500 MB.

## `pdfPassword` (type: `string`):

For password-protected PDFs (used for every file in this run).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "ocrDpi": 300,
  "tables": "lines",
  "markdown": true,
  "layoutText": false,
  "outputMode": "document",
  "maxPagesPerFile": 1000,
  "maxFileSizeMb": 100
}
```

# Actor output Schema

## `documents` (type: `string`):

One row per file (or per page in page mode). Download as JSON, CSV or Excel.

## `summary` (type: `string`):

Files read, pages read with and without OCR, errors, events billed.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
        }
    ],
    "ocrLanguages": [
        "eng"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("usefulapi/pdf-text-ocr").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }],
    "ocrLanguages": ["eng"],
}

# Run the Actor and wait for it to finish
run = client.actor("usefulapi/pdf-text-ocr").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "ocrLanguages": [
    "eng"
  ]
}' |
apify call usefulapi/pdf-text-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,usefulapi/pdf-text-ocr"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/RZTKyYFv4CAEwOIaS/builds/bA7jfjjZLjCUdKAzc/openapi.json
