# PDF & Image to Text OCR for AI Agents - No Login (`cliqtomedia/pdf-text-ocr`) Actor

Extract clean text or Markdown from PDF and image URLs. Uses the PDF text layer when present and Tesseract OCR for scanned pages, per page. 20 languages, page ranges, per-page rows. No login, no API key.

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

## Pricing

from $2.20 / 1,000 document (text layer)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Turn public PDF and image URLs into clean text or Markdown. The Actor reads the PDF text layer when a page has one, and runs Tesseract OCR on scanned pages and images, page by page. No login, no API key. A run with one text PDF costs about $0.0075; each OCR page adds $0.012.

Add one URL and click **Start**: a 6-page text PDF plus one image takes about 4 seconds and costs about $0.02. You get one row per document (or one row per page for RAG), with the reading method and OCR confidence for every page.

![PDF & Image to Text OCR: add PDF or image URLs, each page is read from the text layer or with OCR in 20 languages, and you get clean text or Markdown rows, one row per page if you want, with no login](https://api.apify.com/v2/key-value-stores/cXdlP5qlthZr9gYWG/records/README-BANNER-20261013.png)

### Table of contents

- [What it does](#what-it-does)
- [What data you get](#what-data-you-get)
- [Quick start](#quick-start)
- [Input](#input)
- [Output](#output)
- [Pricing](#pricing)
- [Use cases](#use-cases)
- [Scheduling and monitoring](#scheduling-and-monitoring)
- [API, export and integrations](#api-export-and-integrations)
- [Limitations and partial results](#limitations-and-partial-results)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Related Actors](#related-actors)
- [Support](#support)

### What it does

You give public `http(s)` URLs of PDF files or images. For each URL the Actor:

1. Downloads the file and checks its real type from the file content (not from the URL).
2. Reads every selected page of a PDF:
   - **Text layer:** if the page has real text, it is read directly. This is fast and exact.
   - **OCR:** if the page is a scan (no usable text), it is rendered at 300 DPI and read with [Tesseract OCR](https://github.com/tesseract-ocr/tesseract).
3. Reads images (PNG, JPG, TIFF, GIF, BMP, WEBP) with OCR.
4. Saves the text as plain text or Markdown to the Dataset, with page numbers, method (`text` or `ocr`) and OCR confidence.
5. Deletes the downloaded file. Documents and page images are never stored.

#### Good fit

- **AI agents and RAG pipelines:** get clean text from a PDF link, one row per page, ready for chunking.
- **Scanned archives and forms:** read old scans that have no text layer.
- **Mixed batches:** text PDFs, scans and photos of documents in one run, with clear status per file.

### What data you get

For each URL you get one row with the text, the page count, how many pages were read from the text layer and how many with OCR, the PDF title and metadata, and a clear `status` with an error code when something went wrong. In page mode you get one row per page instead. See [Output](#output) for all fields.

### Quick start

1. Open the **Input** tab.
2. Keep the two prefilled `urls` (an IRS W-9 form and a small test image), or add your own PDF or image URL.
3. Keep **OCR mode** `auto` and **OCR languages** `English`.
4. Click **Start**.
5. Open the **Documents** view in the Output tab and check `status`, `textPages`, `ocrPages` and `text`.

Expected result: two rows with `status: complete`. The W-9 has 6 text-layer pages; the image has 1 OCR page.

### Input

#### Input fields

| Field | Plain name | Type | Required | Default / prefill | Allowed values | Effect | Example | Recommendation |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `urls` | PDF or image URLs | array of strings | yes | prefill: W-9 PDF + `eurotext.png` | 1–100 public `http(s)` URLs | Files to read. Duplicates are read once. Objects like `{"url": "..."}` are also accepted. | `["https://www.irs.gov/pub/irs-pdf/fw9.pdf"]` | Use direct file links, not viewer pages. |
| `ocr` | OCR mode | string | no | `auto` | `auto`, `always`, `never` | `auto`: OCR only pages without usable text. `always`: OCR every page. `never`: text layer only; images fail with `OCR_DISABLED`. | `"auto"` | Keep `auto`. Use `always` only when the text layer is broken. |
| `languages` | OCR languages | array of strings | no | `["eng"]` | up to 3 of: `eng`, `deu`, `fra`, `spa`, `ita`, `por`, `nld`, `pol`, `ces`, `swe`, `dan`, `nor`, `fin`, `tur`, `rus`, `ukr`, `ara`, `hin`, `vie`, `ind` | Languages for OCR. Each OCR page is charged once per language. | `["eng", "deu"]` | Pick only the languages the document uses. |
| `pages` | Pages | string | no | empty (all pages) | ranges like `1-3, 5, 10-`, up to 50 ranges | Same page selection for every document. `10-` means page 10 to the end. | `"1-3"` | Use it to skip covers or appendices. |
| `maxPages` | Max pages per document | integer | no | `50` | 1–300 | Upper limit after the page selection. Extra pages are skipped, not charged, and listed in `warnings`. | `100` | Raise it for long text PDFs. |
| `outputFormat` | Text format | string | no | `text` | `text`, `markdown` | `markdown` re-flows paragraphs, keeps bullet and numbered lists, makes short ALL-CAPS lines headings and adds page markers. | `"markdown"` | Use `markdown` for LLM input. |
| `outputMode` | Rows | string | no | `document` | `document`, `page` | `document`: one row per document with a `pages` list. `page`: one row per page. | `"page"` | Use `page` for RAG chunking. |
| `preserveLayout` | Keep column layout | boolean | no | `false` | `true`, `false` | Keeps the physical layout of text-layer pages (columns, spacing). No effect on OCR pages. | `true` | Use for forms and invoices with aligned columns. |
| `maxFileSizeMb` | Max file size (MB) | integer | no | `50` | 1–100 | Larger files are not downloaded and get `FILE_TOO_LARGE`. | `100` | Raise only for big scanned books. |

Empty `urls` stops the run with `INVALID_INPUT`. An API call with `{}` does not use the prefill.

#### Interacting settings

| When | Input A | Input B | Effective behavior | Example | Cost/result effect |
| --- | --- | --- | --- | --- | --- |
| Page selection is long | `pages` | `maxPages` | `pages` is applied first, then `maxPages` cuts the rest | `pages: "1-"`, `maxPages: 10` → pages 1–10 | Skipped pages are not charged |
| Several languages | `languages` | `ocr` | Languages are used only on OCR pages | `["eng","fra"]` on a text PDF | No OCR, no language charge |
| OCR is off | `ocr: never` | `outputMode` | A scan gives one row with no text: `text: ""` and pages with `method: none` in document mode, `text: null` and `method: none` in page mode | Scanned PDF | One `document` event: the PDF was opened and checked |
| Page rows | `outputMode: page` | text-layer pages | One `document` event per started 50 text-layer page rows | 300 text pages → 6 events | See [Pricing](#pricing) |

#### Input examples

##### Read a text PDF and an image

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
  ]
}
```

Expected behavior: the W-9 is read from its text layer (6 pages); the image is read with English OCR.
Expected output: 2 document rows with `status`, `textPages`, `ocrPages`, `text`.
Cost: 1 `document` + 1 `ocr-page` + start ≈ $0.0195 on the Free plan.

##### Page rows in Markdown for RAG

```json
{
  "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"],
  "outputMode": "page",
  "outputFormat": "markdown",
  "pages": "1-3"
}
```

Expected behavior: pages 1–3 are read and saved as 3 rows.
Expected output: `pageNumber`, `method`, `charCount`, `text` per row.

### Output

Results go to the default **Dataset** (view **Documents**). The run summary goes to the key-value store record `RUN_SUMMARY`. You can export the Dataset as JSON, CSV, Excel or HTML.

#### Output example

Input: the first example above. Two rows; `text` and `pages` are trimmed.

```json
[
  {
    "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "finalUrl": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "status": "complete",
    "fileType": "pdf",
    "contentType": "application/pdf",
    "fileSizeBytes": 140815,
    "pageCount": 6,
    "pagesProcessed": 6,
    "pagesTruncated": false,
    "textPages": 6,
    "ocrPages": 0,
    "format": "text",
    "ocrLanguages": null,
    "charCount": 37820,
    "text": "W-9\n\nForm\n(Rev. March 2024)\nDepartment of the Treasury\nInternal Revenue Service\n\nRequest for Taxpayer\nIdentification Number and Certification…",
    "pages": [
      { "pageNumber": 1, "method": "text", "charCount": 5703, "ocrConfidence": null, "text": "W-9\n\nForm\n(Rev. March 2024)…", "error": null }
    ],
    "title": "Form W-9 (Rev. March 2024)",
    "metadata": { "subject": "Request for Taxpayer Identification Number and Certification", "creator": "Designer 6.5", "pdfVersion": "1.7" },
    "encrypted": false,
    "error": null,
    "warnings": [],
    "fetchedAt": "2026-10-13T05:23:31.200Z",
    "processedAt": "2026-10-13T05:23:31.254Z"
  },
  {
    "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png",
    "status": "complete",
    "fileType": "png",
    "pageCount": 1,
    "textPages": 0,
    "ocrPages": 1,
    "ocrLanguages": ["eng"],
    "charCount": 414,
    "text": "The (quick) [brown] {fox} jumps!\nOver the $43,456.78 <lazy> #90 dog\n& duck/goose, as 12.5% of E-mail\nfrom aspammer@website.com is spam.…",
    "pages": [
      { "pageNumber": 1, "method": "ocr", "charCount": 414, "ocrConfidence": 90.2, "text": "The (quick) [brown] {fox} jumps!…", "error": null }
    ],
    "title": null,
    "metadata": null,
    "error": null,
    "warnings": []
  }
]
```

OCR is not perfect: check `ocrConfidence`. Low values (below about 60) usually mean a blurry scan, the wrong language or handwriting.

#### Output fields

| Field | Meaning | Type | Can be empty? | Example / enum | Notes |
| --- | --- | --- | --- | --- | --- |
| `url` | URL from your input | string | no | `https://www.irs.gov/pub/irs-pdf/fw9.pdf` | Kept exactly as given |
| `finalUrl` | URL after redirects | string | yes, when not downloaded | same as `url` | |
| `status` | Result of this row | string | no | `complete`, `partial`, `not_found`, `error` | See the table below |
| `fileType` | Type found in the file content | string | yes | `pdf`, `png`, `jpg`, `tiff`, `gif`, `bmp`, `webp` | |
| `contentType` | Content-Type sent by the server | string | yes | `application/pdf` | Document rows |
| `fileSizeBytes` | Downloaded size | integer | yes | `140815` | Document rows |
| `pageCount` | All pages in the file | integer | yes | `6` | `1` for single images |
| `pagesProcessed` | Pages read with success | integer | no | `6` | Document rows |
| `pagesTruncated` | `maxPages` cut the selection | boolean | no | `false` | Document rows |
| `textPages` | Pages read from the text layer | integer | no | `6` | Document rows |
| `ocrPages` | Pages read with OCR | integer | no | `0` | Document rows |
| `pageNumber` | Page number, from 1 | integer | page rows only | `1` | Page rows |
| `method` | How the page was read | string | page rows only | `text`, `ocr`, `none` | `none` = no text and OCR off |
| `ocrConfidence` | Tesseract mean word confidence 0–100 | number | yes | `90.2` | `null` for text-layer pages |
| `format` | Text format | string | no | `text`, `markdown` | |
| `ocrLanguages` | OCR languages used | array | yes | `["eng"]` | `null` when no page used OCR |
| `charCount` | Characters in `text` | integer | no | `37820` | |
| `text` | The extracted text | string | yes | `"W-9\n\nForm\n(Rev. March 2024)…"` | `null` on error; `""` when a page has no text |
| `pages` | Per page: `pageNumber`, `method`, `charCount`, `ocrConfidence`, `text`, `error` | array | no | see example | Document rows |
| `title` | Title from PDF metadata | string | yes | `Form W-9 (Rev. March 2024)` | `null` when the PDF has none |
| `metadata` | PDF metadata as in the file | object | yes | `author`, `subject`, `creator`, `producer`, `creationDate`, `pdfVersion` | Not verified. Document rows |
| `encrypted` | PDF is encrypted | boolean | yes | `false` | It may still be readable |
| `error` | Error of this row: `code`, `message` | object | yes | `{"code": "NOT_FOUND", ...}` | `null` when there is no error |
| `warnings` | Notes | array | no | `["Only the first 50 of 120 pages were processed (maxPages)."]` | Empty array when there is nothing to say |
| `fetchedAt` | When the file was downloaded (ISO 8601) | string | yes | `2026-10-13T05:23:31.200Z` | `null` when not downloaded |
| `processedAt` | When the row was written (ISO 8601) | string | no | `2026-10-13T05:23:31.254Z` | |

`null` means "not known or not applicable". `0` is a real count. A page with no text after OCR is `complete` with `text: ""`; it is a result, not an error. `complete` means the file was read; it does not mean the text is long. A scan read with `ocr: never` is `complete` with a warning: in document mode `text` is `""` and each entry in `pages` has `method: none`; in page mode the single row has `text: null` and `method: none`.

#### Output entities and surfaces

| Situation | Surface | Result shape | User action |
| --- | --- | --- | --- |
| Normal file | Dataset row | `status: complete`, `text`, `pages` | Use `text` |
| Some pages failed or the run stopped mid-document | Dataset row | Document mode: `status: partial`, `pages[].error`, `warnings`. Page mode: failed pages are rows with `status: error`; a stop is shown in `warnings` and `RUN_SUMMARY` | Check failed pages, run again for them |
| File not found (HTTP 404/410) | Dataset row | `status: not_found`, `error.code: NOT_FOUND` | Check the URL |
| Other problem | Dataset row | `status: error`, `error.code`, `text: null` | See [Troubleshooting](#troubleshooting) |
| Run totals | Key-value store `RUN_SUMMARY` | `status`, `message`, `counts`, `errorCodes` | Check `chargedDocuments` and `ocrPageEvents` |

`RUN_SUMMARY.status` is one of `SUCCEEDED`, `PARTIAL`, `ERROR`, `INVALID_INPUT`, `CHARGE_LIMIT_REACHED`, `DEADLINE_REACHED`, `TOO_MANY_FAILURES`, `CANCELLED`. The run ends as FAILED only for invalid input or a platform error; a bad URL is an error row, and the run still succeeds.

### Pricing

#### Pricing unit

This Actor uses pay-per-event pricing. You pay only for these events:

| Event | When it is charged | Free | Bronze | Silver | Gold+ |
| --- | --- | ---: | ---: | ---: | ---: |
| Actor start | Once per run, per GB of memory (default 1 GB) | $0.005 | $0.005 | $0.005 | $0.005 |
| `document` | A PDF read from the text layer (all its text-layer pages together), also when it has no text layer and OCR is off. In page mode: one per started 50 text-layer page rows. | $0.0025 | $0.0024 | $0.0023 | $0.0022 |
| `ocr-page` | One page or image read with OCR, once per selected language | $0.012 | $0.0115 | $0.011 | $0.0105 |

Not charged: `not_found` and `error` rows, failed pages, pages skipped by `pages` or `maxPages`, and documents not processed because the run stopped. A run where every URL fails costs only the start event. Platform usage (compute) is included in the event prices.

The Actor checks your **Maximum cost per run** before every page. When the next page would go over it, the run stops, saves what it has, and marks the rest `CHARGE_LIMIT_REACHED`.

#### Cost examples (Free plan, 1 GB, English OCR)

| Scenario | Input scope | Events | Cost |
| --- | --- | ---: | ---: |
| Prefill | 1 text PDF + 1 image | 1 `document` + 1 `ocr-page` | $0.0195 |
| 10 text PDFs | 10 URLs | 10 `document` | $0.030 |
| 100 text PDFs | 100 URLs (one run) | 100 `document` | $0.255 |
| 1,000 text PDFs | 10 runs of 100 URLs | 1,000 `document` | $2.55 |
| 10 scanned pages | 1 scan, 10 pages | 10 `ocr-page` | $0.125 |
| 100 scanned pages | 1 run | 100 `ocr-page` | $1.205 |
| 1,000 scanned pages | about 7–10 runs (see speed below) | 1,000 `ocr-page` | about $12.04–12.05 |
| 10 scanned pages, 2 languages | `languages: ["eng","deu"]` | 20 `ocr-page` | $0.245 |

### Use cases

| Buyer job | Input path | Output entity | Key fields | Next integration |
| --- | --- | --- | --- | --- |
| Give an AI agent the text of a PDF link | `urls` | document row | `text`, `pageCount`, `title` | MCP tool call, LLM prompt |
| Split documents into pages for RAG | `urls`, `outputMode: page` | page row | `pageNumber`, `text`, `method` | Chunking, vector database |
| Read a scanned archive | `urls`, `ocr: auto`, `languages` | document row | `ocrPages`, `pages[].ocrConfidence`, `text` | Search index, review queue |
| Get headings and lists as Markdown | `urls`, `outputFormat: markdown` | document row | `text`, `format` | Notes, knowledge base |
| Read only part of long reports | `urls`, `pages: "1-3"` | document row | `pagesProcessed`, `text` | Summaries, alerts |

### Scheduling and monitoring

The Actor reads the URLs you give it at run time; it does not watch for changes. To read the same documents again on a timer (for example, a price list PDF that is updated every week), create an Apify schedule with a fixed input and compare `text` or `charCount` with the last run in your own system. Each run is charged again.

### API, export and integrations

- **API:** start the Actor with `POST https://api.apify.com/v2/acts/cliqtomedia~pdf-text-ocr/run-sync-get-dataset-items` and the input JSON. You get the Dataset rows in the response.
- **Export:** download the Dataset as JSON, CSV, Excel, XML or HTML from the Output tab or the API.
- **Webhooks:** use Apify webhooks to send rows to your system when a run finishes.
- **No-code:** use the Apify integrations for Make, Zapier or n8n to pass `text` to the next step.

#### Use with AI agents (MCP)

- MCP server URL with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/pdf-text-ocr`

- Smallest useful input:

  ```json
  { "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"] }
  ```

- Key output fields: `status` (did it work), `text` (the content), `pageCount`, `ocrPages` (how many pages needed OCR), `error.code` (what to fix).

- A text PDF takes a few seconds and costs $0.0075 with the start event. Each OCR page adds about 10 seconds and $0.012 per language.

- Limits: up to 100 URLs and 300 pages per document; public URLs only. For long scans, use `pages` or `maxPages` so the call returns in time.

### Limitations and partial results

#### What it does not do

- No handwriting. Tesseract reads printed text; handwritten text gives poor or empty results.
- Tables are not rebuilt. Table text comes out as lines, without columns.
- No Chinese, Japanese or Korean OCR. PDFs in these languages still work if they have a text layer.
- No files behind a login, cookies or a private network. No uploads: only URLs.
- No DOCX, HTML or EPUB. An HTML page gives the error `UNSUPPORTED_FILE_TYPE`.
- A multi-page TIFF is read with one OCR call, which must finish within the time limit of one OCR page (50 seconds at 1 GB). Use PDF for long scans.

#### Speed and limits

- Up to 100 URLs per run. 100 text PDFs took 31 seconds in our test.
- Up to 300 pages per document (`maxPages`). A 300-page text PDF took about 17 seconds.
- OCR takes about 10 seconds per normal scanned page at 1 GB and up to 30–40 seconds for very dense small print. Plan for about 100–150 OCR pages per 30-minute run at 1 GB. At 2 GB OCR is about twice as fast for the same price per page.
- Memory: 1–2 GB (default 1 GB). Default run timeout: 1,800 seconds. The Actor stops 25 seconds before the timeout and saves what it has (`DEADLINE_REACHED`).
- File size: up to 100 MB (`maxFileSizeMb`, default 50). Images: up to 40 megapixels.
- Slow or hanging servers: one URL may take up to 45 seconds, one OCR page up to 50 seconds (half at 2 GB). If failed downloads, unreadable documents and failed pages waste more than 100 seconds in one run, the run stops and the rest are marked `TOO_MANY_FAILURES`, not charged.

#### Partial results

In document mode, a document gets `status: partial` when some pages were read and others failed (for example one OCR page timed out) or the run stopped in the middle of it (deadline or cost limit). Read pages keep their text; failed pages have `pages[].error`. Only read pages are charged. In page mode there is no `partial` status: each page row is `complete` or `error`, and a stop is listed in `warnings` and `RUN_SUMMARY`.

### Troubleshooting

#### The row has `UNSUPPORTED_FILE_TYPE`

Symptom: `status: error`, `error.code: UNSUPPORTED_FILE_TYPE`.
Likely reason: the URL opens an HTML page (a viewer or a download page), not the file itself.
Check: open the URL in a private browser window. It should download or show the raw PDF or image.
Action: use the direct file link.

#### The text is empty or strange

Symptom: `charCount` is low or the text has wrong characters; `ocrConfidence` is low.
Likely reason: wrong OCR language, a blurry scan, handwriting, or a broken text layer.
Check: `pages[].method` and `pages[].ocrConfidence`.
Action: set the right `languages`. If `method` is `text` but the text is garbage, set `ocr` to `always`.

#### `PDF_PASSWORD_REQUIRED` or `PDF_DAMAGED`

The PDF needs a password or is broken. The Actor cannot open these files. Use a copy without a password.

#### `DEADLINE_REACHED` or `TOO_MANY_FAILURES`

`DEADLINE_REACHED`: the run was close to its timeout. Use a longer timeout, 2 GB memory, or fewer pages. `TOO_MANY_FAILURES`: too much time was lost on slow or failing URLs. Check those URLs and run the rest again. Unprocessed rows are not charged.

#### `CHARGE_LIMIT_REACHED`

The run reached your **Maximum cost per run**. Raise the limit or read fewer pages.

### FAQ

#### Do I need a login or an API key for the documents?

No. The Actor reads public URLs only. It does not send cookies or passwords, and it does not open private network addresses.

#### Do you keep my documents?

No. Each file is downloaded to a temporary folder and deleted after it is read. Only the extracted text is saved to your Dataset. Make sure you have the right to process the documents you send.

#### Why did I pay for OCR on a PDF?

In `auto` mode, pages without usable text (scans, or pages with fewer than 25 visible characters or mostly broken glyphs) are read with OCR. Use `ocr: never` if you want the text layer only. With `ocr: never` a PDF is still charged as one `document`, also when it turns out to have no text layer.

#### Is OCR 100% correct?

No. Tesseract is good on clean printed text and weaker on low-quality scans, small print, handwriting and complex layouts. Check `ocrConfidence`.

#### Is the output the same as other PDF text extractors?

The text is similar, but field names are different (for example `text` and `status`). Map the fields when you switch.

### Related Actors

- [DOI Metadata & Bibliography Enrichment](https://apify.com/cliqtomedia/doi-bibliography-enrichment) — get paper metadata by DOI, then read the open-access PDF here.
- [YouTube Subtitles & Transcript Scraper API](https://apify.com/cliqtomedia/youtube-transcript-scraper) — text from videos for the same AI or RAG pipeline.

### Support

For bugs, questions or feature requests, open an issue in the **Issues** tab of this Actor. Include the run ID, your input settings and the `error.code`. Do not send private documents, tokens or passwords.

OCR by [Tesseract](https://github.com/tesseract-ocr/tesseract) (Apache-2.0); PDF text by [Poppler](https://poppler.freedesktop.org/) (GPL, run as a separate program).

# Changelog

This Actor's version history is a separate document: https://apify.com/cliqtomedia/pdf-text-ocr/changelog.md

# Actor input Schema

## `urls` (type: `array`):

Up to 100 public http(s) URLs of PDF files or images (PNG, JPG, TIFF, GIF, BMP, WEBP). The file type is detected from the file content, not the URL. No login, cookies or private networks; duplicates are processed once. Files are deleted when the run ends.

## `ocr` (type: `string`):

auto (default): use the PDF text layer and run OCR only on pages without usable text (scans). always: OCR every page, even if it has a text layer. never: text layer only, no OCR (images then fail with OCR_DISABLED). Text-layer pages are covered by one per-document charge; OCR is charged per page and per selected language.

## `languages` (type: `array`):

Languages of the document, used by OCR only. Up to 3. Each OCR page is charged once per selected language, and more languages make OCR slower, so pick only the languages the document uses. Default: English.

## `pages` (type: `string`):

Optional page selection for every document, e.g. 1-3, 5, 10- (10 to the end). Empty: all pages, up to Max pages per document.

## `maxPages` (type: `integer`):

Upper limit of processed pages per document (after the page selection). Extra pages are skipped, not charged, and reported in warnings. 1–300, default 50.

## `outputFormat` (type: `string`):

text (default): clean plain text. markdown: paragraphs re-flowed, bullet and numbered lists, short ALL-CAPS lines as headings, page markers. Tables are not rebuilt.

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

document (default): one row per document with full text and a pages\[] list; text-layer pages are charged as one document event. page: one row per page, ready for chunking and RAG; text-layer pages are charged as one document event per started 50 page rows. A scan with OCR off gives one row, charged as one document event.

## `preserveLayout` (type: `boolean`):

Keep the physical layout of text-layer pages (columns, aligned spacing). Off by default, which gives reading-order text. Does not affect OCR pages.

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

Larger files are not downloaded and get error FILE_TOO_LARGE. 1–100, default 50.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
  ],
  "ocr": "auto",
  "languages": [
    "eng"
  ],
  "maxPages": 50,
  "outputFormat": "text",
  "outputMode": "document",
  "preserveLayout": false,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

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

Document or page rows as a table.

## `runSummary` (type: `string`):

Status, counts of documents and pages, charged text and OCR pages, error codes.

# 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 = {
    "urls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/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 = { "urls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/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 '{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
  ]
}' |
apify call cliqtomedia/pdf-text-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/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/2ZUNjDW59anWWdS6X/builds/1yB2gjv40qjiwzHUB/openapi.json
