# PDF Text Extractor - OCR, Markdown & JSON (`datascraperes/pdf-text-extractor`) Actor

Extract clean text, Markdown and JSON from public PDF URLs, including scanned pages with OCR for research, automation and AI workflows.

- **URL**: https://apify.com/datascraperes/pdf-text-extractor.md
- **Developed by:** [DataScraperES](https://apify.com/datascraperes) (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 $1.00 / 1,000 pdf page extracteds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PDF Text Extractor | OCR, Markdown & JSON

Extract text from public PDF links and receive clean, structured results ready
for research, search, archiving, document analysis and AI workflows.

The Actor supports normal digital PDFs and scanned documents. It can return
plain text, Markdown or structured JSON, identify pages that may need OCR,
highlight pages that look like tables, and optionally include page-level text
blocks and coordinates.

### Pricing at a glance

You are charged by the pages actually processed, not by the number of PDF
files and not by the total number of pages reported by the document.

| Processed page | Price |
| --- | ---: |
| Page processed without OCR | **$0.001** |
| Page processed with OCR | **$0.002** |

There is **no separate charge for starting a run** and no fixed charge per PDF.

Examples:

| Work completed | Actor charge |
| --- | ---: |
| 1 normal page | $0.001 |
| 1 OCR page | $0.002 |
| 100 normal pages | $0.100 |
| 80 normal pages + 20 OCR pages | $0.120 |
| 1,000 normal pages | $1.000 |
| 1,000 OCR pages | $2.000 |

Each processed page is charged exactly once. If OCR is enabled and a page is
read with OCR, that page uses the OCR price instead of the normal price. A
failed download or a PDF that cannot produce a result does not create page
charges.

You can also set **Maximum cost per run** in Apify run options. The Actor uses
the actual processed-page count for billing and stops publishing new results
when the configured charge limit is reached.

### What this Actor does

For every direct PDF URL, the Actor:

1. Downloads the PDF from the supplied public link.
2. Reads its text and document structure.
3. Detects pages with little or no selectable text.
4. Uses OCR on those pages when OCR is enabled.
5. Returns the selected output format in the Dataset.
6. Reports the pages processed, pages flagged for OCR and pages that may contain
   tables.

The Actor processes only the PDF links you provide. It does not crawl links
inside a document, create embeddings, build a vector database or automatically
create a complete RAG system.

### Best use cases

- Convert research papers, reports and public studies into searchable text.
- Prepare Markdown documents for AI assistants and knowledge bases.
- Extract text from manuals, regulations and technical documentation.
- Process scanned reports, faxes and image-based documents with OCR.
- Convert public product catalogues and brochures into structured data.
- Inspect page layout, text blocks and coordinates.
- Build document archives and search indexes.
- Process several PDF files in one run while keeping one result per file.

### Quick start

1. Open the Actor's **Input** tab.
2. Add one or more direct PDF links.
3. Choose `Markdown`, `JSON` or `Plain text`.
4. Leave OCR enabled when the document may contain scans or image-only pages.
5. Set `maxPages` if you want to limit processing and page charges.
6. Launch the run.
7. Open the **Dataset** tab to inspect or download the results.

The default configuration is suitable for a first test: Markdown output, OCR
enabled, a maximum of 50 pages per PDF and a maximum file size of 100 MB.

For integrations and automation, optional fields can be omitted when you want
to use these defaults. Do not send `null` for an optional field; use a valid
value or leave the field out of the input.

### Input reference

| Field | Type | Required | Default | Limits and description |
| --- | --- | --- | --- | --- |
| `pdfUrls` | Array of strings | Yes | — | 1–50 direct `http` or `https` PDF links. Duplicate URLs are processed only once. |
| `outputFormat` | String | No | `markdown` | `markdown`, `json` or `text`. |
| `ocrEnabled` | Boolean | No | `true` | Read pages flagged for OCR. When disabled, processed pages use the normal page price. |
| `ocrLanguage` | Select | No | `eng` | Choose one of the 20 supported OCR languages from the dropdown. |
| `maxPages` | Integer | No | `50` | 1–5,000 pages per PDF. Only pages actually processed count toward billing. |
| `includePageData` | Boolean | No | `false` | Adds detailed page blocks, coordinates and layout data to the result. |
| `maxDownloadMb` | Integer | No | `100` | 1–500 MB maximum file size per PDF. Larger files are rejected. |

#### `pdfUrls`

Use a direct link that returns a PDF file, for example:

```text
https://example.org/documents/annual-report.pdf
```

A page containing an embedded PDF viewer is not a direct PDF link and cannot be
processed. The URL must be accessible without a login or personal browser
session.

You can submit up to 50 URLs in one run. Duplicate URLs are processed only
once. Empty or invalid URLs are rejected before processing.

#### `outputFormat`

- `markdown`: recommended for AI workflows, search and human-readable
  documents. Headings, lists and other detected structure are preserved when
  available.
- `json`: recommended for structured automation. The `content` field contains
  the parser's structured JSON result.
- `text`: recommended when you only need the extracted text as a string.

#### `ocrEnabled` and `ocrLanguage`

OCR is useful for scans, faxes and image-only pages. The Actor analyses every
processed page and exposes candidates in `ocrPages` and `pages[].needsOcr`.

When `ocrEnabled` is `true`, pages identified as requiring OCR are processed
with OCR and use the `$0.002` page price. Other processed pages use `$0.001`.

When `ocrEnabled` is `false`, no OCR is run. All processed pages use the
`$0.001` normal page price, even if the analysis reports that a page could
benefit from OCR. The page remains visible in `ocrPages` so you can identify
where OCR would have been useful.

The Apify form provides a controlled dropdown, so you do not need to type a
language code manually. The available choices are:

| Code | Language |
| --- | --- |
| `eng` | English |
| `spa` | Spanish |
| `fra` | French |
| `deu` | German |
| `ita` | Italian |
| `por` | Portuguese |
| `nld` | Dutch |
| `pol` | Polish |
| `rus` | Russian |
| `ukr` | Ukrainian |
| `ara` | Arabic |
| `hin` | Hindi |
| `jpn` | Japanese |
| `kor` | Korean |
| `chi_sim` | Chinese (Simplified) |
| `chi_tra` | Chinese (Traditional) |
| `tur` | Turkish |
| `vie` | Vietnamese |
| `tha` | Thai |
| `heb` | Hebrew |

For multilingual documents, choose the language that appears most often. OCR
accuracy depends on scan resolution, contrast, rotation, font quality and the
selected language.

#### `maxPages`

`maxPages` is applied separately to every PDF. If a document has 1,000 pages
and `maxPages` is `50`, the Actor processes at most 50 pages and can create at
most 50 page charges for that PDF.

The `totalPages` field shows the total page count reported by the PDF. It is
informational. The `processedPages` field shows the number used for billing.

#### `includePageData`

Keep this option disabled when you only need extracted text. Enable it when you
need detailed information about text blocks, positions, annotations, forms or
layout.

Page-level details can make the Dataset considerably larger, especially for
long documents. They do not create an additional per-page event charge; billing
still follows processed pages.

#### `maxDownloadMb`

This limit protects the run from unexpectedly large files. A PDF above the
configured limit is reported as an error and does not produce page charges.

### Input examples

#### Recommended Markdown extraction

```json
{
  "pdfUrls": [
    "https://example.org/reports/annual-report.pdf",
    "https://example.org/reports/market-study.pdf"
  ],
  "outputFormat": "markdown",
  "ocrEnabled": true,
  "ocrLanguage": "eng",
  "maxPages": 50,
  "includePageData": false,
  "maxDownloadMb": 100
}
```

#### Spanish scanned document

```json
{
  "pdfUrls": [
    "https://example.org/documentos/informe-escaneado.pdf"
  ],
  "outputFormat": "markdown",
  "ocrEnabled": true,
  "ocrLanguage": "spa",
  "maxPages": 100,
  "includePageData": false,
  "maxDownloadMb": 200
}
```

#### Structured JSON with page details

```json
{
  "pdfUrls": [
    "https://example.org/manuals/product-manual.pdf"
  ],
  "outputFormat": "json",
  "ocrEnabled": true,
  "ocrLanguage": "eng",
  "maxPages": 25,
  "includePageData": true,
  "maxDownloadMb": 50
}
```

#### Low-cost text extraction without OCR

```json
{
  "pdfUrls": [
    "https://example.org/publications/digital-document.pdf"
  ],
  "outputFormat": "text",
  "ocrEnabled": false,
  "maxPages": 20,
  "includePageData": false,
  "maxDownloadMb": 25
}
```

### Dataset output

The Actor normally creates one Dataset item for each PDF URL. Successful items
have `status: "ok"`. Failed files have `status: "error"` so one unavailable
URL does not hide successful results from the same run. If a run-level charge
limit is reached before a result can be published, that result is reported as
`not_charged` in `SUMMARY` and is not added to the Dataset.

#### Successful result fields

| Field | Description |
| --- | --- |
| `status` | `ok` when the PDF was processed successfully. |
| `sourceUrl` | The URL supplied in `pdfUrls`. |
| `finalUrl` | The final URL after redirects, when it differs from `sourceUrl`. |
| `filename` | A safe filename generated from the PDF URL. |
| `sha256` | SHA-256 hash of the downloaded PDF file. |
| `fileSizeBytes` | Size of the downloaded PDF in bytes. |
| `format` | `markdown`, `json` or `text`. |
| `totalPages` | Total pages reported by the PDF parser. This is not the billing count. |
| `processedPages` | Pages actually processed. This is the page count used for billing. |
| `textChars` | Number of extracted text characters. |
| `ocrPages` | Page numbers flagged as candidates for OCR by the page analysis. |
| `tableLikelyPages` | Page numbers that appear likely to contain tables. This is a detection hint, not a guarantee of perfect table extraction. |
| `pages` | Compact page-by-page analysis for the processed pages. |
| `durationSeconds` | Download and processing time for this PDF. |
| `content` | Extracted text, Markdown or structured JSON, depending on `format`. |
| `pageData` | Detailed raw page information when `includePageData` is `true`. |

#### Page analysis fields

Every entry in `pages` describes one processed page:

| Field | Description |
| --- | --- |
| `page` | Page number, starting at 1. |
| `textChars` | Characters extracted from that page. |
| `needsOcr` | Whether the analysis considers OCR useful for the page. |
| `reasons` | Reasons behind the OCR recommendation, when available. |
| `tableLikely` | Whether the page appears to contain a table. |
| `columnCount` | Estimated number of text columns. |
| `contentBounds` | Bounding box of detected page content, when available. |

`ocrPages` and `needsOcr` describe the document analysis. They do not mean that
OCR was executed when `ocrEnabled` is `false`.

#### `content` by format

- With `markdown`, `content` is a Markdown string.
- With `text`, `content` is a plain text string.
- With `json`, `content` is a structured object containing the parser's JSON
  representation, including page content and extracted text when available.

### Example result

The following is an illustrative successful Dataset item. Hashes, timings,
character counts and page classifications vary by document.

```json
{
  "status": "ok",
  "sourceUrl": "https://example.org/reports/annual-report.pdf",
  "finalUrl": "https://example.org/reports/annual-report.pdf",
  "filename": "001_annual-report.pdf",
  "sha256": "7f4d8b7a8c0f3a1a4d2c8f9e7b6a5d4c3b2a190817161514131211100f0e0d0c",
  "fileSizeBytes": 284731,
  "format": "text",
  "totalPages": 12,
  "processedPages": 3,
  "textChars": 8421,
  "ocrPages": [2],
  "tableLikelyPages": [3],
  "pages": [
    {
      "page": 1,
      "textChars": 3120,
      "needsOcr": false,
      "reasons": [],
      "tableLikely": false,
      "columnCount": 1,
      "contentBounds": [55.2, 72.1, 488.4, 690.7]
    },
    {
      "page": 2,
      "textChars": 0,
      "needsOcr": true,
      "reasons": ["no-text"],
      "tableLikely": false,
      "columnCount": 1,
      "contentBounds": [48.0, 65.0, 500.0, 710.0]
    },
    {
      "page": 3,
      "textChars": 5301,
      "needsOcr": false,
      "reasons": [],
      "tableLikely": true,
      "columnCount": 2,
      "contentBounds": [48.0, 65.0, 500.0, 710.0]
    }
  ],
  "durationSeconds": 1.84,
  "content": "Annual report\\n\\nRevenue increased during the reporting period..."
}
```

When `outputFormat` is `json`, the same top-level structure is returned but
`content` is an object instead of a string. When `includePageData` is enabled,
the item also contains detailed raw information for every processed page.

### Error result example

If a URL cannot be downloaded or the PDF cannot be processed, the Dataset item
has an explicit error status:

```json
{
  "status": "error",
  "sourceUrl": "https://example.org/reports/missing.pdf",
  "filename": "001_missing.pdf",
  "errorType": "HTTPStatusError",
  "error": "Client error '404 Not Found' for url",
  "durationSeconds": 0.42
}
```

Error text can vary by server and document. Failed files do not produce page
charges.

### Run summary

The Actor also saves a `SUMMARY` record for the run. It is useful when several
PDFs are submitted together:

```json
{
  "status": "partial",
  "processed": 1,
  "failed": 1,
  "total": 2,
  "format": "markdown",
  "ocrEnabled": true,
  "maxPages": 50,
  "durationSeconds": 3.18,
  "items": [
    {
      "sourceUrl": "https://example.org/reports/annual-report.pdf",
      "status": "ok",
      "totalPages": 12,
      "processedPages": 3,
      "textChars": 8421,
      "durationSeconds": 1.84
    },
    {
      "sourceUrl": "https://example.org/reports/missing.pdf",
      "status": "error",
      "totalPages": null,
      "processedPages": null,
      "textChars": null,
      "durationSeconds": 0.42
    }
  ]
}
```

Summary statuses:

- `ok`: every submitted URL produced a successful result.
- `partial`: at least one PDF succeeded and at least one failed.
- `error`: no PDF produced a successful result, or processing stopped before a
  result could be published.

### Understanding billing with the output

Use these fields to reconcile a run:

1. `processedPages` is the total number of pages used for billing for that PDF.
2. With OCR enabled, count pages where `needsOcr` is `true` for the OCR portion.
3. The remaining processed pages use the normal page price.
4. With OCR disabled, all `processedPages` use the normal page price.

For example, a result with 80 normal pages and 20 OCR pages costs:

```text
(80 × $0.001) + (20 × $0.002) = $0.120
```

The `totalPages` value is never used instead of `processedPages`. This is why a
1,000-page PDF limited to 50 pages cannot generate 1,000 page charges.

### Important limitations

- The URL must be a direct, accessible PDF link.
- Login-protected, private, encrypted or password-protected PDFs may fail.
- The Actor does not use a personal browser session to access documents.
- OCR quality depends on the original scan and selected language.
- `tableLikelyPages` is a layout signal, not a promise of cell-perfect table
  extraction.
- Complex figures, handwriting, unusual fonts and heavily damaged scans may
  produce incomplete text.
- `includePageData` can make outputs much larger for long documents.
- The Actor does not follow links inside PDFs or process non-PDF web pages.

### Frequently asked questions

#### Is pricing per PDF or per page?

Per processed page. There is no fixed PDF charge and no separate run-start
charge.

#### What happens with a 1,000-page PDF?

Set `maxPages` to the amount you want to process. For example, with
`maxPages: 50`, at most 50 pages are processed and billed. Without that limit,
the charge follows the pages actually processed: `$1.00` for 1,000 normal
pages or `$2.00` for 1,000 OCR pages.

#### Does every page cost the OCR price when OCR is enabled?

No. Only pages actually processed with OCR use `$0.002`. Other processed pages
use `$0.001`.

#### What if I disable OCR?

The Actor processes pages without OCR and charges every processed page at
`$0.001`. Pages that could benefit from OCR remain visible in `ocrPages` and
`needsOcr`.

#### Why can `totalPages` be greater than `processedPages`?

`totalPages` is the complete page count reported by the PDF. `processedPages`
is limited by `maxPages` and by the pages the Actor actually completed.

#### Does `includePageData` cost extra per page?

No additional page event is created. It can increase output size and processing
work, so use it only when coordinates and detailed layout data are useful.

#### What happens if one URL fails in a batch?

The Actor keeps successful results, adds an error item for the failed URL and
marks the run summary as `partial` when appropriate.

#### Can I submit a PDF viewer URL?

No. Submit the URL that downloads the `.pdf` file itself, not the page that
embeds or displays the document.

#### Can the Actor create embeddings or a vector database?

No. It returns extracted content. You can download the Dataset and pass the
text or Markdown to the search, indexing or AI system of your choice.

#### How can I keep a large run within budget?

Use `maxPages`, choose OCR only when needed, keep `includePageData` disabled
unless required, and set **Maximum cost per run** in Apify run options.

### Responsible use

Process documents that you are allowed to access and use. Do not submit
confidential or personal documents unless your handling of that information is
authorized and appropriate. Respect the terms of the website hosting each PDF
and any applicable copyright, privacy and data-protection requirements.

# Actor input Schema

## `pdfUrls` (type: `array`):

Add one or more direct public links to PDF files. Viewer pages are not supported.

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

Choose Markdown for AI and search workflows, JSON for structured data, or plain text for simple extraction.

## `ocrEnabled` (type: `boolean`):

Automatically read pages that do not contain selectable text, such as scans, faxes and image-only PDFs.

## `ocrLanguage` (type: `string`):

Choose the main language used in scanned pages. The list uses valid Tesseract language codes.

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

Limit the number of pages processed from each PDF to control time and usage.

## `includePageData` (type: `boolean`):

Include text blocks, coordinates and other page details when you need to inspect document layout.

## `maxDownloadMb` (type: `integer`):

Skip PDF files larger than this limit.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "outputFormat": "markdown",
  "ocrEnabled": true,
  "ocrLanguage": "eng",
  "maxPages": 50,
  "includePageData": false,
  "maxDownloadMb": 100
}
```

# Actor output Schema

## `dataset` (type: `string`):

One result per PDF with text, Markdown or JSON and optional page details.

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

See how many PDFs were processed successfully, partially or with errors.

# 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 = {
    "pdfUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ],
    "outputFormat": "markdown",
    "ocrEnabled": true,
    "ocrLanguage": "eng",
    "maxPages": 50,
    "includePageData": false,
    "maxDownloadMb": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("datascraperes/pdf-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 = {
    "pdfUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
    "outputFormat": "markdown",
    "ocrEnabled": True,
    "ocrLanguage": "eng",
    "maxPages": 50,
    "includePageData": False,
    "maxDownloadMb": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("datascraperes/pdf-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 '{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "outputFormat": "markdown",
  "ocrEnabled": true,
  "ocrLanguage": "eng",
  "maxPages": 50,
  "includePageData": false,
  "maxDownloadMb": 100
}' |
apify call datascraperes/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datascraperes/pdf-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/FP1nEZ6K8GFeq3DIc/builds/FhE65IYFQGaqkYMz5/openapi.json
