# PDF Text Extractor 📄 (Markdown, OCR & metadata) (`tagadanar/pdf-text-extractor`) Actor

PDF to text or Markdown for AI and RAG: headings, tables and links kept, scanned pages read by OCR, metadata, per-page rows and ready-made chunks. No API key.

- **URL**: https://apify.com/tagadanar/pdf-text-extractor.md
- **Developed by:** [Tagada Data](https://apify.com/tagadanar) (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 $2.80 / 1,000 pdf documents

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 Markdown or text, with OCR for scanned pages

Give it PDF links and get back clean text or Markdown you can feed straight into an embedding model, a search index or an LLM prompt. Headings, lists, links and tables are kept, repeated page headers and footers are dropped, and scanned pages with no text layer are read by built-in OCR. Everything runs inside the actor: no API key, no external OCR or AI service.

$4 per 1,000 PDFs of up to 100 pages (longer PDFs count once more per extra 100 pages), plus $12 per 1,000 pages that needed OCR. Platform usage is included. Pages that already have a text layer cost nothing extra. If you ask for one row per page or per chunk, each row also costs $0.20 per 1,000 rows.

### What you get

By default one row per PDF:

| Field | Description |
| --- | --- |
| `url`, `title`, `author`, `subject`, `keywords` | Document details. `title` falls back to the first main heading when the file has no title (`titleSource` tells you which) |
| `creationDate`, `modifiedDate`, `creator`, `producer`, `pageCount` | More metadata, dates as ISO strings |
| `text`, `format` | The extracted text, as Markdown or plain text |
| `wordCount`, `charCount`, `pagesExtracted` | Size of what was read |
| `ocrPages`, `ocrConfidence` | How many pages were read by OCR and their average confidence (0 to 100) |
| `pages` | One entry per page: `pageNumber`, `method` (`text`, `ocr` or `empty`), `wordCount`, `ocrConfidence`, `rotatedDegrees` |
| `links` | Links found in the PDF, with the page they are on |
| `chunks` | Only when a chunk size is set: `chunkIndex`, `text`, `wordCount`, `pageStart`, `pageEnd`, `charStart`, `charEnd` |
| `warnings` | Anything that limited the result: page limit hit, pages skipped, OCR off on a scanned page |
| `status`, `errorCode`, `errorMessage` | `ok`, or an error row (see below) |

Choose **One row per** Page to get a row for every page, or Chunk to get a row for every text chunk, each carrying the document title, URL and page numbers. Page and chunk rows are billed at $0.0002 each on top of the document fee, because the platform bills every dataset row. A document too large for one dataset row (several thousand pages of text) is delivered as page rows automatically.

#### Markdown output

With the Markdown format you get `#` headings (from font sizes), `-` lists, `[text](url)` links, and tables written as Markdown tables when the PDF lays out aligned columns. Plain text keeps the paragraphs and writes table rows as `cell | cell | cell`.

#### Example row (real, from a run on 2026-10-16, text shortened)

```json
{
  "type": "document",
  "status": "ok",
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "title": "Form W-9 (Rev. March 2024)",
  "titleSource": "metadata",
  "subject": "Request for Taxpayer Identification Number and Certification",
  "creationDate": "2024-03-06T13:18:13.000Z",
  "pageCount": 6,
  "pagesExtracted": 1,
  "format": "markdown",
  "text": "Form W-9 Request for Taxpayer Give form to the\n\n(Rev. March 2024) Identification Number and Certification requester. Do not ...",
  "wordCount": 919,
  "ocrPages": 0,
  "pages": [{ "pageNumber": 1, "method": "text", "wordCount": 919, "ocrConfidence": null, "rotatedDegrees": 0 }],
  "chunks": [{ "chunkIndex": 0, "wordCount": 92, "charStart": 0, "charEnd": 553, "pageStart": 1, "pageEnd": 1 }]
}
```

### Scanned PDFs and OCR

OCR mode **Auto** (the default) checks every page. Pages with a real text layer are read directly. Pages without one (scans, photographed pages, image-only exports) are rendered and read with Tesseract, and you pay the OCR page fee for those only. Mixed documents work page by page. **Force** reads every page with OCR, useful when a PDF's hidden text layer is garbled. **Off** never uses OCR and tells you which pages were skipped.

Seven languages are bundled: English, French, German, Spanish, Italian, Portuguese and Dutch. Pick up to two per run for mixed documents. Sideways and upside-down scans are detected and turned upright. Handwriting is not supported.

OCR is slow work. Expect roughly 10 to 15 seconds per page at the default memory, so a 100 page scan takes about 20 minutes. Text-layer PDFs are fast, a few seconds for hundreds of pages. Raise the run memory for faster OCR (it scales the CPU), the cost per page stays about the same.

### Errors are rows, not crashes

A link that cannot be read produces an error row (`status: "error"`) and costs nothing:

| `errorCode` | Meaning |
| --- | --- |
| `not-a-pdf` | The link returned a web page or another file type (the content type is named) |
| `password-protected` | The PDF is encrypted. Enter a password in the input if you have one |
| `not-found` | HTTP 404 or 410 |
| `too-large` | Over the 60 MB file limit |
| `corrupt` | The PDF is damaged or unreadable |
| `no-text` | A readable PDF where no text was found (a scan with OCR off, or OCR found nothing) |
| `fetch-failed` | The server refused or timed out on every attempt |

The run status message at the top of the run summarizes the same, and a run where every link failed ends as failed instead of pretending to succeed.

### Limits

- 60 MB per PDF, 500 PDFs per run, at most 1,000 pages per PDF (default 100, set with **Max pages per document**). A PDF is billed once per started block of 100 pages read. When the page limit cuts a document, its `warnings` say how many pages were left.
- Each PDF is downloaded directly first. If the server refuses, a datacenter proxy is tried. Files behind a login are not supported, but links with a signature (Apify private store links, pre-signed storage links) work.
- Tables are detected from aligned text. Tables drawn as images, or read by OCR, come out as lines of text rather than Markdown tables.
- Two-column pages are read column by column. Complex magazine layouts can still come out in a surprising order.
- If you set a maximum charge for the run and it is reached, extraction stops there and the status message says so.

### Input examples

Markdown for every page of a few reports:

```json
{
  "urls": [
    { "url": "https://arxiv.org/pdf/1706.03762" },
    { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }
  ],
  "outputFormat": "markdown"
}
```

RAG chunks of about 1,000 characters with a 150 character overlap, one row per chunk:

```json
{
  "urls": [{ "url": "https://arxiv.org/pdf/1706.03762" }],
  "outputRows": "chunk",
  "chunkSize": 1000,
  "chunkOverlap": 150
}
```

A French scan, pages 1 to 10 only:

```json
{
  "urls": [{ "url": "https://example.com/scanned-contract.pdf" }],
  "ocrMode": "auto",
  "ocrLanguages": ["fra", "eng"],
  "pageRange": "1-10"
}
```

### Who uses this

- **AI and RAG builders.** Turn a folder of PDF links into chunked, page-referenced text for embeddings and agents.
- **Operations and finance teams.** Pull text out of invoices, statements and contracts, scanned or not, to feed a parser or a review queue.
- **Researchers and analysts.** Convert papers and reports to Markdown with titles, authors and dates for notes and datasets.
- **Developers.** Call it from the API as a PDF to text endpoint without running OCR yourself.

### FAQ

**Can it extract text from a scanned PDF?** Yes. Leave OCR on Auto and pages without a text layer are read with OCR in the languages you pick.

**How do I convert a PDF to Markdown?** Run it with the Markdown text format. Headings, lists, links and detected tables come out as Markdown.

**Can I get the text page by page?** Set **One row per** to Page, or read the `pages` list on each document row.

**How do I split a PDF into chunks for RAG?** Set a chunk size (and overlap). Chunks break at paragraph and sentence ends and carry their page numbers.

**Is there a size limit?** 60 MB and up to 1,000 pages per PDF, 500 PDFs per run.

**Does it need an API key?** No. OCR and parsing run inside the actor.

**What about images instead of PDFs?** Use [Image OCR Extractor](https://apify.com/tagadanar/image-ocr-extractor), the sibling actor for PNG, JPG, WebP, GIF, BMP and TIFF links.

### Keywords

pdf text extractor, pdf to text, pdf to markdown, pdf ocr, scanned pdf to text, extract text from pdf, pdf parser, pdf metadata, pdf for rag, pdf chunking.

# Changelog

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

# Actor input Schema

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

Links to PDF files, one per line (or upload a list). Any public https link works, including files stored in an Apify key-value store or dataset. Each PDF is downloaded once and read inside the actor.

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

Markdown keeps headings, lists, links and detected tables, which is what most AI and RAG pipelines want. Plain text drops the markup.

## `outputRows` (type: `string`):

Document: one row per PDF with the full text, metadata and a page list. Page: one row per page. Chunk: one row per text chunk, sized below, ready for embedding.

## `chunkSize` (type: `integer`):

Split the text into chunks of about this many characters, cut at paragraph or sentence ends. 0 means no chunks. With "One row per: Chunk" and 0 here, 1,000 is used. Document rows get a chunks list when this is above 0.

## `chunkOverlap` (type: `integer`):

Characters repeated at the start of the next chunk so a sentence cut at a boundary is still found. At most half the chunk size.

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

Auto reads with OCR only the pages that have no text layer (scans, photographed pages) and charges per OCR page. Off never uses OCR. Force runs OCR on every page, useful when the text layer is garbled.

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

Languages printed on the scanned pages. Pick up to 2 for mixed documents; each extra language slows OCR.

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

Only read these pages, e.g. 1-5, 8, 12-. Leave empty for all pages.

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

Stops reading a PDF after this many pages (within the selected pages). Each started block of 100 pages is billed as one PDF, and long scanned documents take minutes per hundred pages, so raise this on purpose. Hard limit 1,000.

## `removeHeadersFooters` (type: `boolean`):

Drops lines such as page numbers and running titles that repeat at the top or bottom of most pages (documents of 4 pages or more). Turn off to keep every line.

## `password` (type: `string`):

Only for password-protected PDFs. The same password is tried on every file; files that do not open are reported as password-protected.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    },
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "outputFormat": "markdown",
  "outputRows": "document",
  "chunkSize": 0,
  "chunkOverlap": 100,
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "maxPagesPerDocument": 100,
  "removeHeadersFooters": true
}
```

# Actor output Schema

## `results` (type: `string`):

One item per PDF, page or chunk in the default dataset.

# 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": [
        {
            "url": "https://arxiv.org/pdf/1706.03762"
        },
        {
            "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tagadanar/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 = { "urls": [
        { "url": "https://arxiv.org/pdf/1706.03762" },
        { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tagadanar/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 '{
  "urls": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    },
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ]
}' |
apify call tagadanar/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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