# PDF to Text Extractor with OCR: PDF, DOCX & Images (`phaux/pdf-docx-ocr-text-extractor`) Actor

Extract text from PDF, Word (DOCX) and image files by URL; scanned pages are read with OCR (7 languages). Use to turn documents into text for search or LLMs; optional tables to CSV/JSON. Input: fileUrls. Output per file: status, text, per-page text, page count, OCR pages, metadata, tables.

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

## Pricing

from $2.50 / 1,000 document converteds

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 to Text Extractor with OCR: PDF, DOCX & Images

Convert **PDF to text**, run **OCR** on scanned PDFs and images, and pull the text out of **Word DOCX** files, from a list of links or uploads.

Give it **links to PDF, Word (DOCX) or image files**, or files you uploaded to an Apify key-value store, and get back **the full text, the text of every page, the page count and document metadata** for each one. Scanned PDF pages are **detected automatically, page by page**, and read with **Tesseract OCR**; pages that already have a text layer are copied exactly and never OCR'd.

### Quick start for AI agents and API users

- **What it does:** Extract text from PDF, Word (DOCX) and image files by URL; scanned pages are read with OCR (7 languages).
- **When to use it:** To turn documents into text for search or LLMs; optional tables to CSV/JSON.
- **Example input** (the form's prefill; it finishes in under a minute):

```json
{
  "fileUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}
```

- **Example output:** one real dataset row from run `dm6pSZzNothxg4lrf` (2026-10-23), with long fields shortened and empty fields left out. Fields not shown are listed in the output table below.

```json
{
  "source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "sourceType": "url",
  "status": "ok",
  "format": "pdf",
  "fileName": "dummy.pdf",
  "fileSizeBytes": 13264,
  "pageCount": 1,
  "pagesProcessed": 1,
  "truncated": false,
  "ocrUsed": false,
  "ocrPages": 0,
  "ocrPagesCharged": 0,
  "ocrMode": "auto",
  "ocrLanguages": "eng",
  "charCount": 14,
  "wordCount": 3
}
```

- **Cost (pay per event, Free-plan prices):** Document converted $0.0025; OCR page $0.004. Failed items are not charged.
- **From an AI agent:** with the Apify MCP server, call `call-actor` with actor `phaux/pdf-docx-ocr-text-extractor`, or load it as its own tool with `https://mcp.apify.com?tools=phaux/pdf-docx-ocr-text-extractor`. Read results with `get-dataset-items`.

### What does PDF, DOCX & Image Text Extractor do?

- **PDF:** reads the embedded text layer with Poppler, with title, author, creator, producer, dates, PDF version, page size and the encrypted and tagged flags.
- **Scanned PDF pages:** in `auto` mode, a page whose text layer has fewer than 25 letters or digits *and* that contains an image is rendered at 300 DPI and OCR'd. `always` OCRs every page; `never` turns OCR off.
- **DOCX:** body paragraphs and tables in reading order, split at the page breaks Word stored in the file. Headers, footers, footnotes and endnotes go into `metadata.headersFootersNotes`.
- **Images:** PNG, JPEG, TIFF (every frame), GIF, BMP and WEBP are OCR'd.
- **OCR languages:** English, German, French, Spanish, Portuguese, Italian and Dutch (`ocrLanguages`, for example `eng+deu`). Each OCR page reports its mean word confidence.
- **One dataset item per input**, with `status` `ok`, `empty`, `low_confidence` or `failed` and a machine-readable error code.

Text is returned **unchanged**: nothing is filtered, redacted or "cleaned". Document contents are **never written to the run log**.

### Why use it?

- **Pay for OCR only where it's needed:** in a mixed PDF only the scanned pages are OCR'd and charged, and pages where OCR confidence is below 40 are returned but not charged.
- **Safe on untrusted links:** only public http(s) hosts (private and internal IPs are refused on every redirect hop), file-size, page and OCR-page caps, time and memory limits on every Poppler and Tesseract process, and decompression-bomb guards.
- **Encrypted PDFs handled:** PDFs with only an owner password (copy/print restrictions) are read and flagged in `metadata.encrypted`; PDFs that need a password to open are reported as `encrypted` and not charged.
- **Never times out:** a time budget finishes the run SUCCEEDED before its timeout and keeps everything done so far.

Use cases: RAG and LLM ingestion of PDFs and Word files, invoice and contract text capture, digitising scanned archives, search indexing, and AI agents that need "the text of this file".

### How to use it

1. Put file links in `fileUrls`. With an empty input, the Actor runs a 1-page example PDF (W3C test file) in a few seconds.
2. Or upload files to a key-value store (Console → Storage → Key-value stores, or `PUT /v2/key-value-stores/{storeId}/records/{key}` with the raw file as the body), pick that store in `keyValueStore`, and optionally list record keys in `keyValueStoreKeys`. The run gets **read-only access to that one store only**.
3. Leave `ocrMode` on `auto` unless you need OCR forced on or off.
4. Need tables? Turn on `extractTables` (see the tutorial below).
5. Run it and open the **Documents** view, or download JSON, CSV or Excel. For LLM pipelines use `text` (whole document) or `pages[].text`.

### Tutorial: extract tables from a PDF to CSV and JSON

Turn on **Extract tables to CSV / JSON** (`extractTables: true`) and the Actor also returns every table it finds in the PDF's text layer (with [pdfplumber](https://github.com/jsvine/pdfplumber)) or in a DOCX file. Each table comes back twice: as JSON rows in the dataset item, and as a CSV file in the run's key-value store. It's off by default, and **turning it on costs nothing extra**: you still pay the same `document-converted` event per document.

**Worked example: the 2026 federal General Schedule pay table.** The U.S. Office of Personnel Management publishes it as a one-page PDF (a public-domain U.S. Government work). Input:

```json
{
  "fileUrls": ["https://www.opm.gov/policy-data-oversight/pay-leave/salaries-wages/salary-tables/pdf/2026/GS.pdf"],
  "extractTables": true
}
```

Result of a real run (22 Oct 2026, 15 s for this PDF and a 7-page Census report together; charged 1 `document-converted` per document, the same as with tables off). The item keeps all its usual fields (`text`, `pages`, `metadata` ...) and adds:

```json
{
  "tableCount": 1,
  "tableStrategy": "lines",
  "tables": [
    {
      "page": 1,
      "index": 1,
      "rowCount": 16,
      "columnCount": 12,
      "csvKey": "TABLE-88317177a9a7-p1-t1.csv",
      "csvUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/TABLE-88317177a9a7-p1-t1.csv",
      "rows": [
        ["Grade", "Step 1", "Step 2", "Step 3", "Step 4", "Step 5", "Step 6", "Step 7", "Step 8", "Step 9", "Step 10", "WITHIN GRADE AMOUNTS"],
        ["1", "$ 22,584", "$ 23,341", "$ 24,092", "$ 24,840", "$ 25,589", "$ 26,028", "$ 26,771", "$ 27,519", "$ 27,550", "$ 28,248", "VARIES"],
        ["2", "25,393", "25,997", "26,839", "27,550", "27,858", "28,677", "29,496", "30,315", "31,134", "31,953", "VARIES"],
        "... 12 more rows ...",
        ["15", "126,384", "130,597", "134,810", "139,023", "143,236", "147,449", "151,662", "155,875", "160,088", "164,301", "4,213"]
      ]
    }
  ]
}
```

The CSV file (`csvUrl`) opens directly in Excel or Google Sheets, or with `pandas.read_csv`. Grades 4–13 are left out below:

```csv
Grade,Step 1,Step 2,Step 3,Step 4,Step 5,Step 6,Step 7,Step 8,Step 9,Step 10,WITHIN GRADE AMOUNTS
1,"$ 22,584","$ 23,341","$ 24,092","$ 24,840","$ 25,589","$ 26,028","$ 26,771","$ 27,519","$ 27,550","$ 28,248",VARIES
2,"25,393","25,997","26,839","27,550","27,858","28,677","29,496","30,315","31,134","31,953",VARIES
3,"27,708","28,632","29,556","30,480","31,404","32,328","33,252","34,176","35,100","36,024",924
14,"107,446","111,028","114,610","118,192","121,774","125,356","128,938","132,520","136,102","139,684","3,582"
15,"126,384","130,597","134,810","139,023","143,236","147,449","151,662","155,875","160,088","164,301","4,213"
```

Every grade and step comes out in the right cell. The three-line header "WITHIN / GRADE / AMOUNTS" is joined into one cell, and no OCR is needed because the PDF has a text layer.

**Use it from code.** `rows[0]` is usually the header, so this gives one JSON object per row:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("phaux/pdf-docx-ocr-text-extractor").call(run_input={
    "fileUrls": ["https://www.opm.gov/policy-data-oversight/pay-leave/salaries-wages/salary-tables/pdf/2026/GS.pdf"],
    "extractTables": True})
for doc in client.dataset(run["defaultDatasetId"]).iterate_items():
    for t in doc.get("tables") or []:
        header, *body = t["rows"]
        records = [dict(zip(header, r)) for r in body]
        print(t["page"], t["index"], records[0])
```

**When it works well, and when it doesn't.**

- **Ruled tables** (cell borders or row rules), such as pay tables, price lists, invoices, forms and most report tables, work best with the default `tableStrategy: "lines"`.
- **Stacked columns:** some reports draw borders only around column blocks, so each cell holds a whole column of numbers. The Actor splits those back into rows. In the same test run, the Census Bureau's *New Residential Construction* PDF gave 12 tables, for example 17 rows × 11 columns on page 3. The row labels (months) sat outside the ruled area there, so they were not part of the table, and a few cells kept a stray space from the PDF ("3 36.9"). Check tables like that before you use them.
- **Borderless tables:** try `tableStrategy: "text"`, which guesses columns from text alignment. It is noisier: titles and footnotes can become rows, and wide cells can split.
- **Dense multi-block layouts**, such as the IRS 1040 tax table, where several narrow tables sit side by side inside one ruled box, come back merged into a few large cells. Use `text` / `pages` for those.
- **Scanned PDFs and images:** tables are only read from text layers, so OCR pages are skipped (with a `tables_skipped_ocr_pages` warning). Their text is still extracted as usual.
- Up to 500 tables per document. If the tables are too big to fit in the dataset item, `rows` is left out and the CSV files still hold everything.

### Input

```json
{
  "fileUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
  "keyValueStore": "<storeId>",
  "keyValueStoreKeys": ["scan.pdf"],
  "ocrMode": "auto",
  "ocrLanguages": "eng",
  "maxFileSizeMb": 50,
  "maxPagesPerDocument": 200,
  "maxOcrPagesPerDocument": 50
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `fileUrls` | array | — | Public http(s) links to PDF, DOCX or image files (PNG, JPEG, TIFF, GIF, BMP, WEBP). Each URL is checked against its site's robots.txt and must resolve to a public IP. Leave empty and add no key-value store files to run the small example PDF. |
| `keyValueStore` | string | — | Optional. Pick (or give the ID or name of) a key-value store you uploaded files to. The Actor gets read-only access to this store only. Each record's content must be the raw file. |
| `keyValueStoreKeys` | array | — | Record keys of the files in that store, e.g. 'scan.pdf'. Leave empty to process every record in the store (up to 1,000). |
| `ocrMode` | string | `"auto"` | Auto OCRs a PDF page only when its text layer has fewer than 'Min text characters per page'. Images are always OCR'd unless Never. Allowed: `auto`, `always`, `never`. |
| `ocrLanguages` | string | `"eng"` | Tesseract language codes joined with '+', e.g. 'eng' or 'eng+deu'. Installed: eng, deu, fra, spa, por, ita, nld. |
| `ocrDpi` | integer | `300` | Resolution scanned PDF pages are rendered at before OCR. 300 is Tesseract's sweet spot; lower is faster but less accurate. |
| `minTextCharsPerPage` | integer | `25` | In auto mode, a PDF page whose text layer has fewer letters/digits than this is OCR'd. |
| `maxFileSizeMb` | integer | `50` | Larger files are refused before download (not charged). |
| `maxPagesPerDocument` | integer | `200` | Only the first N pages are extracted; the item is marked truncated. |
| `maxOcrPagesPerDocument` | integer | `50` | Caps OCR work (and OCR charges) per document. Extra scanned pages are left empty and listed in warnings. |
| `includePageTexts` | boolean | `true` | Add the 'pages' array (text per page). The full text is always in 'text'. |
| `extractTables` | boolean | `false` | Also return tables from PDF text layers and DOCX files as JSON rows (`tables`) and CSV files (`csvUrl`). Off by default; no extra charge. |
| `tableStrategy` | string | `"lines"` | PDF table detection: `lines` (ruled tables) or `text` (whitespace-aligned, noisier). |
| `maxConcurrency` | integer | `2` | How many documents are downloaded and processed at once. OCR inside a document uses all CPU cores (~1 core per 4 GB memory). |
| `timeBudgetSecs` | integer | `0` | Stop starting new work after this many seconds and finish SUCCEEDED with what is done. 0 = automatic (run timeout minus a safety margin). |

### Output example

The real output of the prefill example (platform run `HiGo8HrkIxrTqmHly`):

```json
{
  "source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "sourceType": "url",
  "status": "ok",
  "format": "pdf",
  "fileName": "dummy.pdf",
  "fileSizeBytes": 13264,
  "pageCount": 1,
  "pagesProcessed": 1,
  "truncated": false,
  "ocrUsed": false,
  "ocrPages": 0,
  "ocrPagesCharged": 0,
  "ocrMode": "auto",
  "ocrLanguages": "eng",
  "charCount": 14,
  "wordCount": 3,
  "text": "Dummy PDF file",
  "pages": [
    {
      "page": 1,
      "text": "Dummy PDF file",
      "method": "text-layer",
      "chars": 14
    }
  ],
  "metadata": {
    "author": "Evangelos Vlachogiannis",
    "creator": "Writer",
    "producer": "OpenOffice.org 2.1",
    "creationDate": "2007-02-23T17:56:37+02",
    "pdfVersion": "1.4",
    "pageSize": "595 x 842 pts (A4)",
    "tagged": "no",
    "encrypted": "no",
    "javascript": "no",
    "linearized": "no"
  },
  "warnings": [],
  "error": null,
  "extractedAt": "2026-10-20T21:21:40Z",
  "httpContentType": "application/pdf; qs=0.001",
  "sha256": "3df79d34abbca99308e79cb94461c1893582604d68329a41fd4bec1885e6adb4",
  "mimeType": "application/pdf",
  "processingSecs": 1.22
}
```

`pages[].method` is `text-layer` (copied from the PDF), `ocr` (recognised by Tesseract, with `ocrConfidence` 0–100 and `lowConfidence: true` below 40), `docx-xml` (read from the DOCX) or `none` (no text). A page may also carry `ocrSkipped` (`maxOcrPagesPerDocument`, `spending_limit` or `time_budget`) or `ocrError`.

### Output fields

| Field | Type | Description |
|---|---|---|
| `source` | string | Input URL, or kv://storeId/key for key-value store files. |
| `sourceType` | string | 'url' or 'keyValueStore'. |
| `finalUrl` | string or null | URL after redirects, when different. |
| `status` | string | 'ok' (text extracted, charged), 'empty' (readable but no text, not charged), 'low_confidence' (only low-confidence OCR text, returned but not charged) or 'failed' (see error, not charged). |
| `format` | string or null | pdf, docx or image. |
| `mimeType` | string or null | Detected MIME type (from file bytes, not the server header). |
| `fileName` | string or null | File name from Content-Disposition, URL path or record key. |
| `fileSizeBytes` | integer or null | Downloaded size in bytes. |
| `sha256` | string or null | SHA-256 of the file. |
| `pageCount` | integer or null | Total pages in the document (DOCX: as stored by Word, else page-break sections; images: frames). |
| `pagesProcessed` | integer | Pages actually extracted (≤ maxPagesPerDocument). |
| `truncated` | boolean | True when the document had more pages than were processed. |
| `ocrUsed` | boolean | True when at least one page's text came from OCR. |
| `ocrPages` | integer | Pages whose text came from OCR (each is one 'ocr-page' charge). |
| `ocrPagesCharged` | integer | OCR pages charged as 'ocr-page' (OCR pages with mean word confidence below 40 are not charged). |
| `ocrMode` | string | OCR mode used. |
| `ocrLanguages` | string | Tesseract languages used. |
| `charCount` | integer | Characters in text. |
| `wordCount` | integer | Whitespace-separated words in text. |
| `text` | string or null | Full text, pages joined by blank lines. Unchanged, never filtered. Null only when moved to contentKey (item over 9 MB). |
| `pages` | array or null | \[{page, text, method: text-layer|ocr|docx-xml|none, chars, ocrConfidence (0-100, OCR pages), lowConfidence?, ocrSkipped?, ocrError?}]. |
| `metadata` | object | PDF: title, author, creator, producer, creationDate, modificationDate, pdfVersion, pageSize, encrypted, tagged… DOCX: core and app properties, headers/footers/footnotes. Image: width, height, imageFormat, frames, dpi. |
| `warnings` | array | Non-fatal notes: page_limit, ocr_limit, time_budget, no_text_found, content_in_key_value_store. |
| `error` | object or null | {code, error} when status is failed. Codes: invalid_url, blocked_host, dns_error, robots_disallowed, http_error, rate_limited, network_error, file_too_large, kv_not_found, unsupported_format, corrupt_file, encrypted, too_large, ocr_disabled, tool_timeout, not_processed, max_charge_reached, internal_error. |
| `contentKey` | string or null | Key-value store record holding text and pages when the item would exceed 9 MB. |
| `contentUrl` | string or null | URL of that record. |
| `processingSecs` | number or null | Wall time for this document. |
| `extractedAt` | string | UTC timestamp. |
| `httpContentType` | string or null | Content-Type header sent by the server (URL inputs only). |
| `tableCount`, `tables`, `tableStrategy` | only with `extractTables` | `tables`: \[{page (null for DOCX), index, rowCount, columnCount, rows, csvKey, csvUrl}]. Not present when table extraction is off. |

If one item would exceed Apify's 9 MB dataset item limit, the full `text` and `pages` are saved unchanged to a key-value store record, and the item carries `contentKey`, `contentUrl` and a warning. The run also saves `ERRORS` and `RUN_STATS` records in the key-value store.

**Error codes** (`error.code`; never charged):

| Group | Codes |
|---|---|
| Input and network | `invalid_url`, `invalid_input`, `blocked_host` (private/internal IP), `dns_error`, `robots_disallowed`, `http_error`, `rate_limited`, `network_error` |
| File | `file_too_large`, `kv_not_found`, `unsupported_format`, `corrupt_file`, `encrypted`, `too_large` |
| Processing | `ocr_disabled`, `tool_timeout`, `not_processed` (time budget), `max_charge_reached`, `internal_error` |

### How much does it cost?

Pay per event, with the same prices on every Apify plan. Apify platform usage is included.

| Event | Price | When it is charged |
|---|---|---|
| Actor start (`apify-actor-start`) | $0.00005 per GB of memory | Once per run (default 2 GB = $0.0001) |
| Document converted (`document-converted`) | $0.0025 | Once for each document (PDF, DOCX or image) converted successfully, whatever its page count |
| OCR page (`ocr-page`) | $0.004 | Per scanned PDF page or image page read with OCR. Text PDFs and DOCX never use OCR. Set `ocrMode` to `never` to avoid these charges. |

**Never charged:** failed downloads, robots-blocked or private URLs, files over the size limit, unsupported, corrupt or password-protected files, documents with no text, and OCR pages with confidence below 40.

Examples:

- A text PDF of any length (up to `maxPagesPerDocument`, default 200 pages) or a DOCX: **$0.0025**.
- An image (one OCR page): **$0.0065**.
- A 10-page scanned PDF: $0.0025 + 10 × $0.004 = **$0.0425**.
- At the default cap of 50 OCR pages, a scanned PDF costs at most **$0.2025**.

If you set a maximum total charge per run, OCR stops at that limit and the item says so (`ocrSkipped: spending_limit`).

### Use with AI agents, MCP and the API

- **MCP:** add `https://mcp.apify.com?tools=phaux/pdf-docx-ocr-text-extractor` to any MCP client and ask, for example, "extract the text of this PDF: https://…".
- **API:** `POST https://api.apify.com/v2/acts/phaux~pdf-docx-ocr-text-extractor/run-sync-get-dataset-items` with `{"fileUrls": ["https://example.com/file.pdf"]}` returns the items directly. Check `status` first: `ok` means the text is in `text`, `failed` means `error.code` says why.
- Input and output fields are documented in the input and dataset schemas.

### Data source and compliance

- The Actor reads **only the files you give it**: public links, or records in the one key-value store you pick. It never follows links inside documents, never logs in and uses no cookies.
- **Each URL is checked against that site's robots.txt** (RFC 9309) for the user agent `PhauxDocTextBot`. Disallowed files are skipped with `robots_disallowed`, and `Crawl-delay` is honoured (up to 30 s).
- It identifies itself honestly, uses **no proxies and no IP rotation**, and doesn't try to get around bot protection, paywalls or captchas. Requests to one host are at least 1 second apart, and an HTTP 429 stops further requests to that host.
- **Personal data:** the Actor doesn't look for or collect personal data; text is returned as found. If your documents contain personal data, you are responsible for having a lawful basis to process it.

### Limits

- **Memory:** default 2 GB (min 1 GB, max 8 GB). Apify gives about 1 CPU core per 4 GB; OCR at 1 GB was 7–9× slower in our tests, so keep 2 GB or more for scans. Text PDFs and DOCX run fine at 1 GB.
- **Speed at 2 GB (measured):** text PDF, 142 pages in about 2 s; DOCX under 1 s; image about 1.5 s; scanned pages at 300 DPI about 9–15 s each (50 pages in about 12 min). 4–8 GB finishes big scan batches 2–4× sooner.
- **Caps:** 50 MB per file (up to 200), 200 pages per document (up to 2,000), 50 OCR pages per document (up to 500), 1,000 inputs per run.
- **No passwords:** PDFs and DOCX that need a password to open are reported as `encrypted`.
- **DOCX pages are approximate** (a DOCX has no fixed pages), and images inside a DOCX aren't OCR'd.
- **OCR quality** depends on the scan: 300 DPI, straight, high-contrast dark-on-light text works best. Photos of signs, light text on dark backgrounds and handwriting give poor results (flagged `lowConfidence` and not charged).

### Open-source components and licences

- **Poppler** (`pdfinfo`, `pdftotext`, `pdftoppm`) is licensed under the **GNU GPL** (version 2 or 3). It is used unmodified as separate programs. The licence texts and Debian copyright file are in the image under `/licenses/poppler-utils/`, and source is at https://poppler.freedesktop.org/ and https://sources.debian.org/src/poppler/.
- **Tesseract OCR** (https://github.com/tesseract-ocr/tesseract) is © Google LLC and contributors, Apache License 2.0. Its language data (tessdata) is also Apache-2.0. Licence and copyright are in `/licenses/tesseract-ocr/`.
- **Leptonica** (used by Tesseract) is under a BSD-style licence.
- **Python packages:** Apify SDK, httpx, Protego, lxml and Pillow, plus pdfplumber, pdfminer.six and pypdfium2 for the optional table extraction, each under its own permissive licence.
- See `/licenses/THIRD_PARTY_NOTICES.md` and `/licenses/VERSIONS.txt` in the image.

### FAQ

- **Can it extract tables?** Yes, optionally. Set `extractTables: true` to get each table as JSON rows plus a CSV file, at no extra charge. It works on PDF text layers and DOCX files, not on scans.
- **Can it read old .doc, PowerPoint or Excel files?** Not yet. Other formats return `unsupported_format` and are not charged.
- **Why was my URL skipped with `robots_disallowed`?** The site's robots.txt disallows that path for crawlers. Download the file yourself and upload it to a key-value store instead.
- **Why is `status` `low_confidence`?** All of the document's text came from OCR pages with confidence below 40, usually a photo or a poor scan. The text is returned but not charged.
- **Run stopped early?** `RUN_STATS.stoppedByTimeBudget` is true when the time budget was reached; everything done until then is kept. Raise the run timeout or `timeBudgetSecs`.
- **Something isn't working?** Open an issue on the Issues tab with the run ID.

# Actor input Schema

## `fileUrls` (type: `array`):

Public http(s) links to PDF, DOCX or image files (PNG, JPEG, TIFF, GIF, BMP, WEBP). Each URL is checked against its site's robots.txt and must resolve to a public IP. Leave empty and add no key-value store files to run the small example PDF.

## `keyValueStore` (type: `string`):

Optional. Pick (or give the ID or name of) a key-value store you uploaded files to. The Actor gets read-only access to this store only. Each record's content must be the raw file. Example: "my-documents" (a store name or ID in your account).

## `keyValueStoreKeys` (type: `array`):

Record keys of the files in that store, e.g. 'scan.pdf'. Leave empty to process every record in the store (up to 1,000).

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

Auto OCRs a PDF page only when its text layer has fewer than 'Min text characters per page'. Images are always OCR'd unless Never. Default: "auto".

## `ocrLanguages` (type: `string`):

Tesseract language codes joined with '+', e.g. 'eng' or 'eng+deu'. Installed: eng, deu, fra, spa, por, ita, nld. Default: "eng".

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

Resolution scanned PDF pages are rendered at before OCR. 300 is Tesseract's sweet spot; lower is faster but less accurate. Default: 300.

## `minTextCharsPerPage` (type: `integer`):

In auto mode, a PDF page whose text layer has fewer letters/digits than this is OCR'd. Default: 25.

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

Larger files are refused before download (not charged). Default: 50.

## `maxPagesPerDocument` (type: `integer`):

Only the first N pages are extracted; the item is marked truncated. Default: 200.

## `maxOcrPagesPerDocument` (type: `integer`):

Caps OCR work (and OCR charges) per document. Extra scanned pages are left empty and listed in warnings. Default: 50.

## `includePageTexts` (type: `boolean`):

Add the 'pages' array (text per page). The full text is always in 'text'. Default: true.

## `extractTables` (type: `boolean`):

Off by default. When on, tables in PDF text layers (pdfplumber) and DOCX files are also returned: each table as JSON rows in the item's 'tables' field plus a CSV file in the key-value store (csvUrl). Scanned/OCR pages and images are skipped. No extra charge: the document price is the same.

## `tableStrategy` (type: `string`):

PDF only. 'lines' finds tables drawn with borders or rules. 'text' guesses columns from text alignment for borderless tables; check the result. Default: "lines".

## `maxConcurrency` (type: `integer`):

How many documents are downloaded and processed at once. OCR inside a document uses all CPU cores (~1 core per 4 GB memory). Default: 2.

## `timeBudgetSecs` (type: `integer`):

Stop starting new work after this many seconds and finish SUCCEEDED with what is done. 0 = automatic (run timeout minus a safety margin). Default: 0.

## Actor input object example

```json
{
  "fileUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "ocrMode": "auto",
  "ocrLanguages": "eng",
  "ocrDpi": 300,
  "minTextCharsPerPage": 25,
  "maxFileSizeMb": 50,
  "maxPagesPerDocument": 200,
  "maxOcrPagesPerDocument": 50,
  "includePageTexts": true,
  "extractTables": false,
  "tableStrategy": "lines",
  "maxConcurrency": 2,
  "timeBudgetSecs": 0
}
```

# Actor output Schema

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

All documents with text, per-page text, metadata and errors (default dataset, overview view).

## `documentsFull` (type: `string`):

Full items including pages and metadata.

## `errors` (type: `string`):

Inputs that failed, with code and reason.

## `runStats` (type: `string`):

RUN_STATS record: counts, pages, OCR pages, charged events.

# 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 = {
    "fileUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("phaux/pdf-docx-ocr-text-extractor").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 = { "fileUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("phaux/pdf-docx-ocr-text-extractor").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 '{
  "fileUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call phaux/pdf-docx-ocr-text-extractor --silent --output-dataset

```

## MCP server setup

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

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/e6ISZJFWRWoptaZg0/builds/kQcSrKcADp4WPX9ch/openapi.json
