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

Extract text from PDF URLs: full plain text, Markdown with tables, the text of every page, hyperlinks and metadata (title, author, dates). Page ranges, OCR for scanned pages, password-protected files.

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

## Pricing

from $3.15 / 1,000 pdf processeds

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

## PDF Text Extractor - PDF to Text, Markdown & OCR

**PDF Text Extractor** extracts the text of PDF files from any public URL: the whole document as plain text and as Markdown, the text of every page, the clickable links, and the PDF metadata (title, author, subject, keywords, creation and modification dates). Pages that are only an image (scanned paper, faxes, photographed forms) are read with OCR. You paste PDF links and get one clean dataset record per PDF, ready for spreadsheets, databases, search indexes or LLM prompts.

It is the PDF-only sibling of our [Document to Markdown](https://apify.com/fabrikit/document-to-markdown) Actor and uses the same layout engine, priced per PDF instead of per page.

### What you get for each PDF

- **Full text** in reading order: two-column papers are read column by column, running headers, footers and page numbers are removed, hyphenated words are joined.
- **Markdown** with headings (from font sizes), bullet and numbered lists, and tables rebuilt from column alignment.
- **Text of every page** (`pages`) with its word count and whether it came from the text layer or from OCR, so you can cite page numbers.
- **Links** (`links`): every web link annotation in the PDF with the page it is on.
- **Metadata**: title, author, subject, keywords, creator and producer applications, PDF version, creation and modification dates in ISO 8601, page count, file size, detected language.
- **Scan detection**: `scannedPages`, `isScanned` and `ocrPages` tell you which documents were image-only.

### Use cases

- Feed reports, papers, manuals and filings into an LLM, a vector database or a RAG pipeline.
- Build a searchable archive of public documents (government forms, regulations, standards, court or council PDFs).
- Pull the text of research papers from arXiv in bulk (abstract links like `arxiv.org/abs/1706.03762` are converted to the PDF automatically).
- Check a batch of PDFs: page counts, languages, which ones are scans, which links they contain.
- Read only the pages you need, for example the first two pages of every annual report.

### How to use

1. Paste one or more PDF links into **PDF URLs**.
2. Optional: pick **Pages to extract** (`1-3, 7, 10-`), a **Max pages per PDF**, and the OCR mode and language.
3. Run the Actor and download the dataset as JSON, CSV, Excel or HTML, or read it through the API.

#### Input example

```json
{
  "pdfUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf",
    "https://arxiv.org/abs/1706.03762"
  ],
  "pageRange": "",
  "maxPagesPerPdf": 0,
  "ocr": "auto",
  "ocrLanguages": "eng",
  "includePages": true,
  "includeLinks": true,
  "maskContacts": true
}
```

| Option | What it does |
|---|---|
| `pageRange` | Pages to read in every PDF: `1-3, 7`, `10-` (to the end), `-5` (first five). Empty = all. |
| `maxPagesPerPdf` | Hard cap per PDF after the page selection (0 = no cap, up to 3,000). |
| `ocr` | `auto` reads only pages without a text layer, `always` reads every page as an image, `never` skips OCR. |
| `ocrLanguages` | Tesseract codes such as `eng`, `spa`, `eng+spa`, `deu`, `fra`, `por`. |
| `includeText`, `includeMarkdown`, `includePages`, `includeLinks` | Turn parts of the output on or off to keep records small. |
| `maskContacts` | Replaces e-mail addresses and phone numbers with `[email removed]` / `[phone removed]` (US toll-free numbers are kept). On by default; turn it off for your own documents. |
| `pdfPassword` | Opens password-protected PDFs (secret field, never logged). |

#### Output example

Shortened record for the NIST Cybersecurity Framework 2.0 PDF (real values from a test run):

```json
{
  "pdfUrl": "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf",
  "fileName": "NIST.CSWP.29.pdf",
  "fileSizeBytes": 1518858,
  "status": "ok",
  "title": "The NIST Cybersecurity Framework (CSF) 2.0",
  "author": "National Institute of Standards and Technology",
  "keywords": "cybersecurity; Cybersecurity Framework (CSF); cybersecurity risk governance; ...",
  "creator": "Acrobat PDFMaker 23 for Word",
  "producer": "Adobe PDF Library 23.8.53",
  "createdAt": "2024-03-06T15:46:45Z",
  "modifiedAt": "2025-06-04T10:58:23Z",
  "language": "en",
  "pageCount": 32,
  "pagesExtracted": 32,
  "pageRange": "1-32",
  "isScanned": false,
  "ocrPages": 0,
  "wordCount": 9134,
  "tableCount": 2,
  "linkCount": 27,
  "text": "The NIST Cybersecurity Framework (CSF) 2.0\n\nNational Institute of Standards and Technology ...",
  "markdown": "# The NIST Cybersecurity Framework (CSF) 2.0\n\n...\n\n## Abstract\n\n...",
  "pages": [{ "page": 1, "text": "The NIST Cybersecurity Framework (CSF) 2.0\n\nNational Institute of Standards and Technology\n\nThis publication is available free of charge from: https://doi.org/10.6028/NIST.CSWP.29 February 26, 2024", "wordCount": 32, "ocr": false, "hasTextLayer": true }],
  "headings": [{ "level": 2, "text": "Abstract", "page": 2 }],
  "links": [{ "url": "https://crossmark.crossref.org/dialog/?doi=10.6028/NIST.CSWP.29", "page": 1 }],
  "extraPagesCharged": 0
}
```

A link that fails (404, a web page instead of a PDF, a file over the size limit, a wrong password) produces a record with `status: "failed"` and the reason in `error`, and is **not charged**. The run's `SUMMARY` record in the key-value store lists PDFs, pages, OCR pages and errors.

### Pricing

Pay per event, no subscription:

| Event | When |
|---|---|
| `pdf-processed` | once per PDF extracted; the first 50 pages are included |
| `extra-page` | only for pages after the 50th page of the same PDF |
| `ocr-page` | only for pages read with OCR |

See the Pricing tab for current prices; paid Apify plans get lower rates. Set a maximum cost per run in Apify and the Actor stops cleanly at that limit: it extracts fewer pages and marks the record `truncated`.

### How it compares

Documents of up to 50 pages only ever pay the per-PDF event. Very long PDFs (hundreds of pages) are also charged per page after page 50; if your PDFs are mostly very long, or you also need Word, PowerPoint, Excel, HTML or EPUB files and RAG chunks with page numbers, [Document to Markdown](https://apify.com/fabrikit/document-to-markdown) may suit you better.

Limits to know: table detection in PDFs works from column alignment, so simple grids come out well and borderless tables with merged cells are best effort. OCR returns paragraphs, not tables, and noisy scans give noisy text. Formulas and images are not converted.

### Integrations

- **API**: start runs and read the dataset with the Apify API, the JavaScript or Python client, or a single synchronous call (`run-sync-get-dataset-items`).
- **No-code**: Make, Zapier and n8n have Apify modules (run Actor, get dataset items), for example "new PDF in a Google Drive folder → extract text → append to a sheet".
- **AI agents**: the Actor can be called as a tool through the Apify MCP server, so an assistant can read a PDF link during a conversation.
- **Webhooks**: get notified when a run finishes and push the records to your own endpoint.

### FAQ

**Is it legal to extract text from PDFs?** The Actor only downloads the links you give it, like a browser would. Make sure you have the right to process those documents; public government, academic and corporate PDFs are the typical case. E-mails and phone numbers are masked by default so personal contact data does not end up in your dataset by accident.

**Do I need a proxy?** No. Files are downloaded directly. If a host refuses the download (401, 403, 429), the Actor retries through Apify datacenter proxy or your own proxy URLs. Residential proxy is not used.

**Can it read scanned PDFs?** Yes, pages without a text layer go through Tesseract OCR (English by default, Spanish built in, other languages downloaded on first use). Each OCR'd page is billed with `ocr-page`.

**How large can a PDF be?** Up to 200 MB (100 MB by default, `maxFileSizeMb`) and 3,000 pages per PDF.

**Are my files stored?** Downloads are deleted right after extraction; only the dataset record stays in your Apify account.

**Password-protected PDFs?** Supported with the `pdfPassword` field (a wrong or missing password gives a clear error and no charge). PDFs that only restrict copying or printing open without a password.

### Changelog

- **0.1** (October 2026): first release. Text, Markdown, per-page text, links, metadata, page ranges, OCR, contact masking, per-PDF pricing.

### En español

**PDF Text Extractor** extrae el texto de archivos PDF desde cualquier URL pública: el documento completo en texto plano y en Markdown (títulos, listas y tablas), el texto de cada página, los enlaces y los metadatos (título, autor, asunto, palabras clave, fechas de creación y modificación). Las páginas escaneadas se leen con OCR. Puedes elegir páginas (`1-3, 7, 10-`), limitar páginas por PDF y abrir PDF con contraseña. Por defecto oculta correos y teléfonos. Se cobra por PDF (incluye hasta 50 páginas), por página adicional y por página con OCR. Consulta la pestaña Pricing para ver los precios vigentes; los planes de pago de Apify tienen tarifas más bajas. Los enlaces que fallan no se cobran. Para Word, PowerPoint, Excel, HTML o EPUB usa nuestro [Document to Markdown](https://apify.com/fabrikit/document-to-markdown).

# Actor input Schema

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

Direct links to PDF files, one per line. arXiv abstract pages (arxiv.org/abs/...) are turned into their PDF link automatically. Duplicates are skipped. Links that return a web page instead of a PDF are reported as failed and not charged.

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

Optional page selection applied to every PDF, for example 1-3, 7, 10- (from page 10 to the end) or -5 (first five pages). Leave empty to extract all pages.

## `maxPagesPerPdf` (type: `integer`):

Stop after this many pages of each PDF (after the page selection). Useful to keep the cost of very long documents predictable. 0 extracts up to 3,000 pages.

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

Auto: OCR only the pages that have no text layer (scans, photos of paper). Always: OCR every page, even if it has text. Never: skip OCR (scanned pages come out empty). Each OCR'd page is billed with the ocr-page event.

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

Tesseract language codes joined with +, for example eng (default), spa, eng+spa, deu, fra, por, ita, nld, pol, rus, jpn, chi_sim. Up to 5. English and Spanish are built in; other languages are downloaded on first use.

## `includeText` (type: `boolean`):

Fill the text field with the whole document as plain text (tables as tab-separated rows).

## `includeMarkdown` (type: `boolean`):

Fill the markdown field with headings, lists and tables as Markdown, ready for an LLM prompt or a RAG index.

## `includePages` (type: `boolean`):

Add a pages array with the text, word count and OCR flag of each extracted page, so you can cite page numbers.

## `includeLinks` (type: `boolean`):

Add a links array with the clickable web links found in the PDF and the page each one is on.

## `maskContacts` (type: `boolean`):

Replace e-mail addresses and phone numbers in the extracted text with \[email removed] / \[phone removed] and drop mailto: links. US toll-free numbers stay. Turn off for your own documents when you need them verbatim.

## `maxItems` (type: `integer`):

Maximum number of PDFs to process in this run (the first N links).

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

Larger PDFs are skipped and reported as failed (not charged).

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

Optional. Opens password-protected PDFs (the same password is tried on every file). Stored encrypted and never logged.

## `proxyConfiguration` (type: `object`):

Only used to download the files. Each PDF is downloaded directly first; if the host refuses (401/403/429), the download is retried through Apify datacenter proxy or your own proxy URLs. Residential proxy is not used.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf"
  ],
  "pageRange": "",
  "maxPagesPerPdf": 0,
  "ocr": "auto",
  "ocrLanguages": "eng",
  "includeText": true,
  "includeMarkdown": true,
  "includePages": true,
  "includeLinks": true,
  "maskContacts": true,
  "maxItems": 10,
  "maxFileSizeMb": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `pdfs` (type: `string`):

One item per PDF link

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

PDFs extracted and failed, pages, OCR pages, 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.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fabrikit/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.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("fabrikit/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.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call fabrikit/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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