# PDF Text Extractor: PDF and Image to Text, with OCR (`vindr/pdf-text-extractor`) Actor

Turn PDFs and images into clean text and Markdown for AI, RAG and search. Paste links (Google Drive and Dropbox too) or a page that links to PDFs. Get headings, pages, chunks, links, form fields and metadata. OCR for scans and images. $4 per 1,000 PDFs.

- **URL**: https://apify.com/vindr/pdf-text-extractor.md
- **Developed by:** [VindR](https://apify.com/vindr) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 pdfs

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

### What does PDF Text Extractor do?

It turns PDF files and images into clean text and Markdown that you can feed straight into an AI model, a RAG pipeline, a search index or a spreadsheet. Paste links to PDFs or images (Google Drive and Dropbox share links work too), or give it a web page and it finds the PDFs the page links to.

For every PDF you get:

- **Markdown** with headings and lists, and **plain text** in clean paragraphs.
- **The text split the way you need it:** one item per document, one per page, or chunks of a set size for embeddings.
- **Metadata:** title, author, dates, page count, language and more.
- **Links** in the document and **filled-in form fields**.
- **OCR for scanned pages and images** (PNG, JPEG, WebP, GIF, BMP) in 12 languages, if you turn it on.

Running headers, footers and page numbers are removed, and words split with a hyphen at the end of a line are joined again, so the text reads the way a person would read it.

### What data do you get?

| Field | Example |
|---|---|
| File, type and title | handbook.pdf, `pdf` or `image`, "Attention Is All You Need" |
| Markdown | `## 3.1 Encoder and Decoder Stacks` followed by the paragraphs |
| Plain text | the same content without formatting |
| Pages | page count, which pages were extracted, which were read with OCR |
| Size | character and word count |
| Metadata | author, subject, keywords, creator app, created and modified date, language, PDF version |
| Links | every web link in the document |
| Form fields | name and value of every filled-in field |
| Source | the PDF's URL, and the web page it was found on |

### How to use it

1. Click **Try for free** at the top of this page.
2. Add links to PDFs or images under **PDF or image URLs, or web pages**, one per line.
3. Pick an **Output format** (Markdown, plain text or both) and how to **Split output by** (document, page or chunk).
4. For scanned documents and images, turn on **Read scanned pages and images (OCR)** and pick the languages.
5. Click **Start**, then download the results as JSON, CSV or Excel, or use the API.

#### Input examples

Extract a few PDFs as Markdown:

```json
{
    "urls": ["https://arxiv.org/pdf/1706.03762v7", "https://example.com/annual-report.pdf"],
    "outputFormat": "markdown"
}
```

Chunks for a RAG pipeline, about 500 tokens each:

```json
{
    "urls": ["https://example.com/handbook.pdf"],
    "splitBy": "chunk",
    "chunkSize": 2000,
    "chunkOverlap": 200,
    "outputFormat": "markdown"
}
```

Every PDF linked from a web page, with OCR for scanned ones in Norwegian and English:

```json
{
    "urls": ["https://example.com/reports/"],
    "maxPdfsPerPage": 50,
    "ocr": true,
    "ocrLanguages": ["nor", "eng"]
}
```

Text from screenshots or photos of documents, in English and Norwegian:

```json
{
    "urls": ["https://example.com/receipt.jpg", "https://example.com/screenshot.png"],
    "ocr": true,
    "ocrLanguages": ["eng", "nor"]
}
```

### Output example

One document, shortened to keep it readable:

```json
{
    "url": "https://arxiv.org/pdf/1706.03762v7",
    "fileName": "1706.03762v7.pdf",
    "fileType": "pdf",
    "foundOnPage": null,
    "status": "ok",
    "title": "Attention Is All You Need",
    "pdfTitle": null,
    "author": null,
    "creator": "LaTeX with hyperref",
    "producer": "pdfTeX-1.40.25",
    "createdAt": "2024-04-10T21:11:43.000Z",
    "modifiedAt": "2024-04-10T21:11:43.000Z",
    "language": null,
    "pdfVersion": "1.5",
    "pageCount": 15,
    "pagesExtracted": 15,
    "pagesWithoutText": [],
    "ocrPages": [],
    "charCount": 39822,
    "wordCount": 6107,
    "markdown": "Provided proper attribution is provided, Google hereby grants permission...\n\n## Attention Is All You Need\n\n...\n\n### Abstract\n\nThe dominant sequence transduction models are based on complex recurrent or convolutional neural networks...\n\n### 1 Introduction\n\n...",
    "text": "Provided proper attribution is provided, Google hereby grants permission...\n\nAttention Is All You Need\n\n...",
    "links": ["https://github.com/tensorflow/tensor2tensor", "..."],
    "formFields": [],
    "fileSizeBytes": 2215244,
    "truncated": false,
    "note": null,
    "extractedAt": "2026-09-25T10:00:00.000Z"
}
```

With **Split output by: Chunk**, each item has `chunkIndex`, `chunkCount`, `pageStart`, `pageEnd` and the chunk's `markdown` or `text`. With **Page**, each item has `pageNumber` and the page's text.

Files that can't be read are saved too, with `status: "failed"` and an `errorMessage` that says why, so you can see what happened to every file. A `RUN_SUMMARY` record in the key-value store counts the PDFs, pages and OCR pages.

### How much does it cost?

- **$4 per 1,000 PDFs** extracted, whatever their length and however you split the output.
- **$10 per 1,000 pages or images read with OCR**, only when you turn OCR on and only for pages that have no text layer. Pages with normal text are never OCR'd or charged for OCR. An image counts as one OCR page and nothing else.

Examples:

- 100 PDFs with normal text: $0.40.
- 1,000 PDFs with normal text: $4.
- 50 scanned PDFs of 10 pages each, with OCR: $0.20 for the PDFs plus $5 for 500 OCR pages.
- 200 screenshots or photos of documents: $2.

Files that fail (broken links, not a PDF, wrong password) and PDFs where no text is found are not charged. The $5 in free monthly credit on the Apify free plan covers about 1,250 PDFs. Set a maximum cost per run if you want a hard budget: the run stops before it goes over it.

### What can you use it for?

- **RAG and AI assistants.** Turn manuals, reports and policies into Markdown chunks for a vector database.
- **Research.** Pull the text out of papers, filings and public reports to search or summarize them.
- **Document processing.** Read invoices, forms and contracts, including the values in filled-in PDF forms.
- **Monitoring.** Point it at a page that publishes PDFs (tenders, minutes, price lists) and get the text of every new file.
- **Archives.** Make old scanned documents searchable with OCR.
- **Screenshots and photos.** Get the text out of screenshots, photographed pages, receipts and signs.

### FAQ

#### Can it read scanned PDFs and images?

Yes, with **Read scanned pages and images (OCR)** turned on. Images can be PNG, JPEG, WebP, GIF or BMP (convert TIFF files first); small images like screenshots are enlarged before reading, which helps a lot. It reads English, Norwegian, Swedish, Danish, Finnish, German, French, Spanish, Italian, Dutch, Portuguese and Polish. OCR takes a few seconds per page or image (up to about 10 for a dense page), so large scanned files take a while. Clear scans give good results (confidence is usually above 90%). Tables, handwriting and text on dark backgrounds are much harder for OCR and can come out incomplete.

#### How good is the Markdown?

Headings come from the font sizes in the PDF: the largest text becomes `#`, the next `##`, and so on, and numbered headings like "3.1 Background" are recognized too. Bullet lists become Markdown lists. Headings that only differ from the body text by being bold can't be told apart and stay as normal paragraphs. Tables come out as text line by line, not as Markdown tables.

#### What about columns?

Text is read in the order the PDF stores it, which for almost all documents is the reading order, one column after the other. For scanned pages, OCR detects the columns.

#### How do I extract a PDF or image from my computer?

Share it with a link and paste the link. Google Drive and Dropbox share links work as they are: set the file to "anyone with the link can view", copy the link and paste it. The Actor turns the share link into a download link by itself. Any other public link to the file works too.

#### What happens with password-protected PDFs?

Add the password under **PDF password** and it is used for every file that needs one. PDFs that only restrict printing or copying open without a password.

#### Why was a file not charged?

Only PDFs and images that give you text are charged. Broken links, files that aren't PDFs or images, damaged files, wrong passwords, and scanned PDFs or images when OCR is off are saved with a `status` and an `errorMessage` so you know what happened, and cost nothing.

#### Is it legal?

It downloads the files you point it to, like a browser would. When you give it a web page, it only follows links to PDF files and respects the site's robots.txt. The documents may contain personal data or copyrighted text, and you are responsible for having the right to process them.

#### Are there any limits?

Files up to 500 MB (100 MB by default, see **Max file size**). Very long documents are best split by page or chunk: a single dataset item can hold about 8 MB of text, and a document that is longer than that is shortened with a note.

#### Can I use it from my own code?

Yes. Every Apify Actor has an API. See the **API** tab for examples in Python, JavaScript and plain HTTP. It also works with Make, n8n, Zapier and LangChain.

#### Something missing or not working?

Open an issue on the **Issues** tab with a link to the PDF, if you can share it.

### More tools from VindR

- [RSS Feed Reader and News Monitor](https://apify.com/vindr/rss-feed-reader): the full text of news articles and blog posts as Markdown.
- [Sitemap Extractor, URL Status and Broken Link Checker](https://apify.com/vindr/sitemap-url-checker): every page of a website, to find what it publishes.

# Actor input Schema

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

Links to PDF files or images, one per line. Google Drive and Dropbox share links work too (the file must be shared with anyone who has the link). You can also add a web page: the PDFs it links to are found and extracted (see <b>Max PDFs per web page</b>). Images need <b>Read scanned pages and images (OCR)</b> turned on.

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

<b>Markdown</b> keeps headings and lists, which works best for AI and RAG. <b>Plain text</b> is clean paragraphs without formatting.

## `splitBy` (type: `string`):

<b>Document</b>: one item per PDF with all its text. <b>Page</b>: one item per page. <b>Chunk</b>: text split into pieces of about <b>Chunk size</b> characters, ready for embeddings and RAG. You pay per PDF either way.

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

Only for <b>Split output by: Chunk</b>. About 4 characters make one token, so 2,000 characters are roughly 500 tokens. Paragraphs are only cut when they are longer than a chunk.

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

Only for chunks: how much text from the end of one chunk is repeated at the start of the next, so no sentence loses its context.

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

Only for <b>Split output by: Document</b>: add a list with the text of every page to each document item.

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

Leave out lines that repeat at the top or bottom of many pages, like running titles and "Page 3 of 10". Recommended for AI and search.

## `ocr` (type: `boolean`):

Read pages that have no text layer, like scanned documents, and image files (PNG, JPEG, WebP, GIF, BMP) with OCR. Only pages without text are read this way, and each OCR page or image is charged separately (see the README). OCR takes a few seconds per page.

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

The languages in your scanned documents and images. Pick only the ones you need: more languages make OCR slower.

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

Only extract these pages, for example <code>1-5, 8, 12-</code>. Leave empty for all pages.

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

For password-protected PDFs. The same password is tried for every file.

## `maxPdfsPerPage` (type: `integer`):

When you give a web page instead of a PDF, extract at most this many of the PDFs it links to. Only links that end in .pdf are followed, and the site's robots.txt is respected.

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

Skip PDFs larger than this.

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

How many PDFs to download and read at the same time.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1706.03762v7"
  ],
  "outputFormat": "both",
  "splitBy": "document",
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "includePages": false,
  "removeHeadersFooters": true,
  "ocr": false,
  "ocrLanguages": [
    "eng"
  ],
  "maxPdfsPerPage": 20,
  "maxFileSizeMb": 100,
  "maxConcurrency": 3
}
```

# Actor output Schema

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

The extracted text and Markdown: one item per PDF, per page or per chunk, depending on "Split output by".

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

How many PDFs and pages were extracted, how many pages were read with OCR, and why any files failed.

# 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://arxiv.org/pdf/1706.03762v7"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vindr/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": ["https://arxiv.org/pdf/1706.03762v7"] }

# Run the Actor and wait for it to finish
run = client.actor("vindr/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": [
    "https://arxiv.org/pdf/1706.03762v7"
  ]
}' |
apify call vindr/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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