# PDF & Image Text Extractor: Markdown, OCR, RAG Chunks (`hydrocore/pdf-image-text-extractor`) Actor

Extract text, Markdown and metadata from PDF files and images, with OCR for scanned pages. Paste PDF links, image links or web pages: PDFs linked on a page are found for you. Page-by-page text, AI-ready chunks, page ranges. No browser.

- **URL**: https://apify.com/hydrocore/pdf-image-text-extractor.md
- **Developed by:** [Hydro Core](https://apify.com/hydrocore) (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.00 / 1,000 pdf reads

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 & Image Text Extractor: Markdown, OCR, RAG Chunks

Turn PDF files and images into clean, searchable text. Paste links to PDFs, images or **web pages that link to PDFs**, and get the text of every document as **Markdown or plain text**, plus the title, author, dates and page count. Scanned pages are read with **OCR** automatically, and you can split each document into **AI-ready chunks** with page numbers for ChatGPT, RAG pipelines and vector databases.

No browser, no proxies, no API keys. You pay only for documents that were read successfully.

### What you get

For every file, one row in your dataset with:

- **Text** as Markdown (headings, lists, paragraphs) or as plain text, or both
- **Metadata**: title, author, subject, keywords, creator app, creation and modification dates, language, PDF version, page count, file size, and whether the PDF has a form or is encrypted
- **OCR details**: how many pages were scanned images and read with OCR, and the average OCR confidence (0-100)
- **Page-by-page text** (optional): the text of each page, and whether that page came from OCR
- **AI chunks** (optional): pieces of about the size you choose, with overlap and the page numbers each chunk came from
- **Where it was found**: when you paste a web page, every PDF row shows the page it was found on and the link text (for example "Annual Report 2025")
- **Clear errors**: files that could not be read are listed with the reason (not found, too large, password needed, not a PDF). These rows are free.

### How it works

1. **Normal PDFs** (with a text layer) are read directly: fast and exact.
2. **Scanned pages** (pages that are just a picture) are detected automatically and read with OCR. You choose the language.
3. **Images** (PNG, JPG, WebP, BMP, GIF, TIFF) are read with OCR.
4. **Web pages**: if a link is a web page, the actor finds every link to a `.pdf` file on it and reads those PDFs. You can keep only PDFs whose link contains words such as "annual report" or "2025".

### Quick start

1. Click **Try for free**.
2. Under **Files or pages**, paste one link per line, for example:
   - `https://www.irs.gov/pub/irs-pdf/fw9.pdf` (a PDF)
   - `https://www.irs.gov/forms-pubs/about-form-w-9` (a web page that links to PDFs)
3. Pick **Markdown** or **Plain text**, and click **Start**.
4. Open the **Output** tab. Download as JSON, CSV or Excel, or connect the dataset to Make, Zapier, n8n, Google Sheets or your own code via the Apify API.

Tip: to process the results of another scraper, put its dataset ID in **Dataset ID** and the name of its link field in **URL field in dataset**.

### Example output

A real row from a test run (text shortened):

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "type": "pdf",
  "status": "ok",
  "fileName": "fw9.pdf",
  "fileSizeBytes": 140815,
  "title": "Form W-9 (Rev. March 2024)",
  "subject": "Request for Taxpayer Identification Number and Certification",
  "author": "SE:W:CAR:MP",
  "creator": "Designer 6.5",
  "creationDate": "2024-03-06T13:18:13.000Z",
  "language": "en-US",
  "pdfVersion": "1.7",
  "hasForm": true,
  "pageCount": 6,
  "pagesProcessed": 6,
  "ocrPages": 0,
  "isScanned": false,
  "wordCount": 6280,
  "charCount": 37694,
  "markdown": "# Form W-9\n\n(Rev. March 2024)\n\n# Request for Taxpayer Identification Number and Certification\n\nDepartment of the Treasury Internal Revenue Service Go to www.irs.gov/FormW9 for instructions and the latest information. …",
  "pages": [{ "page": 1, "ocr": false, "ocrConfidence": null, "markdown": "…" }],
  "chunks": [{ "index": 0, "text": "…", "pageStart": 1, "pageEnd": 1, "charCount": 1325 }],
  "processedAt": "2026-11-03T02:53:06.010Z"
}
```

A scanned PDF row looks the same, with `"ocrPages": 5`, `"isScanned": true` and an `ocrConfidence` such as `58` (for an old typewritten scan).

### Pricing

Pay per event. You only pay for what was actually read. Prices on the Free plan:

| What | Price |
|---|---|
| Run start | $0.001 per GB of run memory (charged once; one charge at the default 512 MB) |
| PDF read (any number of text pages) | $0.003 per PDF |
| Page or image read with OCR | $0.004 per page or image |
| Web page scanned for PDF links | $0.001 per web page |
| Failed, skipped or not-found files | free |

Paid Apify plans get lower prices: down to $0.002 per PDF and $0.0027 per OCR page. Images are charged only the OCR price, not the PDF price.

**Examples (Free plan prices):**

- 100 normal PDFs: $0.001 + 100 × $0.003 = **about $0.30**
- 1,000 normal PDFs: **about $3.00**
- One 40-page scanned PDF: $0.001 + $0.003 + 40 × $0.004 = **about $0.16**
- 200 receipt or screenshot images: $0.001 + 200 × $0.004 = **about $0.80**
- A company "Investors" page with 12 PDF reports: $0.001 + $0.001 + 12 × $0.003 = **about $0.04**

To cap your spend, set **Max cost per run** in the run options. The actor never goes over it: it stops taking new files when the limit is reached, and if that happens in the middle of a scanned PDF, the remaining scanned pages are skipped and noted on the row. Use **Pages to read** or **Max pages per document** to read only the pages you need.

### Input options

| Option | What it does |
|---|---|
| Files or pages | PDF, image or web page links, one per line |
| Dataset ID / URL field | Read links from another actor's dataset |
| Find PDF links on web pages | Collect the PDFs a web page links to (on by default) |
| Only PDFs whose link contains | Keep only matching PDF links, e.g. "2025", "annual report" |
| Text format | Markdown, plain text, or both |
| Include text page by page | Adds a `pages` list |
| AI chunk size / overlap | Adds a `chunks` list for RAG and vector databases |
| Pages to read | e.g. `1-3, 7, 10-` |
| Max pages per document | Read only the first N pages |
| OCR | Auto (only scanned pages), Always, or Off |
| OCR languages | 30 languages, including German, French, Spanish, Arabic, Hindi, Japanese, Korean and Chinese |
| PDF password | For protected PDFs |
| Respect robots.txt | On by default |

### FAQ

**Do I need any coding skills?**
No. Paste links, click Start, and download the results as Excel, CSV or JSON.

**Which files work?**
PDF files and images (PNG, JPG, WebP, BMP, GIF, TIFF) that are reachable by a public link. Web pages are scanned for links to PDF files.

**What is the difference between Markdown and plain text?**
Markdown keeps the structure: headings (from larger fonts), bullet lists and paragraphs. It works well with ChatGPT, Claude and other AI tools. Plain text keeps the original line breaks of the PDF.

**How does it know a page is scanned?**
In Auto mode, a page with almost no text layer (fewer than 25 characters, adjustable) is treated as a picture and read with OCR. Normal pages are read directly, so you only pay OCR prices for pages that need it.

**How accurate is the OCR?**
Good on clean printed text, such as scans of letters, contracts, forms and screenshots (confidence scores of about 90-95 in our tests). It is weaker on handwriting, photos taken at an angle, and very noisy or faded scans, where some text can be missed or wrong. Each row includes an `ocrConfidence` score so you can spot weak pages.

**Can it read tables?**
The text of tables is extracted, row by row, but not as a structured table. If you need cells and columns, use the text as input to a table tool or an AI model.

**Why are my scanned files slow?**
OCR uses a lot of processing power. With the default 512 MB of memory a full scanned page usually takes 20-40 seconds (a 30-page scan took about 15 minutes in our tests). Give the run 2-4 GB of memory in the run options to read scanned pages up to 4-8 times faster. The OCR price per page stays the same; only the start fee goes up ($0.001 per GB).

**Can it find PDFs on a website for me?**
Yes. Paste the web page (for example an "Investor relations" or "Downloads" page). Every PDF linked from that page is read, with the page and link text saved on each row. It reads links that are in the page's HTML; links added later by JavaScript or hidden behind a login are not found. It does not follow links to other pages.

**Does it work with password-protected PDFs?**
Yes, if you know the password: enter it under **PDF password**.

**What happens if a file fails?**
You get a row with `status: "error"` and the reason, such as "HTTP 404" or "file is over the 50 MB limit". Failed files are never charged.

**What exactly am I charged for?**
A small start fee per run, one "PDF read" for each PDF that was read successfully (no matter how many text pages it has), one "OCR page" for each scanned page or image read with OCR (blank pages where OCR finds no text are free), and one "Web page scanned" for each web page you paste that is searched for PDF links (also when no PDFs are found on it). Error rows are free, and there is no extra charge per dataset row.

**How big can files be?**
Up to 50 MB by default (adjustable up to 500 MB). For very large files, give the run 1 GB of memory or more.

**Is it legal to extract text from PDFs?**
The actor only downloads files from public links, respects each site's robots.txt by default, and waits between requests to the same site. You are responsible for having the right to use the documents you process, especially copyrighted or personal data.

### Support

Found a file that does not work, or need a feature? Open an issue on the **Issues** tab with the link (if it is public) and what you expected. Every issue is read.

# Actor input Schema

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

Links to PDF files (.pdf), images (PNG, JPG, WebP, BMP, GIF, TIFF) or web pages. If a link is a web page, the PDFs it links to are collected (see "Find PDF links on web pages"). One link per line.

## `datasetId` (type: `string`):

Read links from an Apify dataset, for example the output of another scraper. Use it with "URL field in dataset".

## `datasetUrlField` (type: `string`):

Name of the dataset field that holds the link. The field can also hold a list of links.

## `findPdfLinks` (type: `boolean`):

When a link is a web page instead of a file, read the page and extract every PDF it links to. Only links in the page's HTML are found; links added later by JavaScript are not.

## `pdfLinkKeywords` (type: `array`):

Optional. Keep only PDF links whose address or link text contains one of these words, e.g. "annual report" or "2025". Not case-sensitive.

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

Upper limit of PDF links taken from one web page. 0 means no limit.

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

Markdown keeps the document structure and works well with ChatGPT and other AI tools. Plain text keeps the original line breaks.

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

Add a "pages" list with the text of each page, and whether that page was read with OCR.

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

Split each document into chunks for RAG or vector databases, with page numbers on every chunk. About 1,000-2,000 characters works well. 0 turns chunking off.

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

Characters repeated from the end of one chunk at the start of the next, so sentences are not cut off without context.

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

Optional. For example "1-3, 7, 10-" ("10-" means page 10 to the end). Empty means all pages.

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

Read at most this many pages of each PDF. 0 means no limit. Useful to keep costs down when you only need the first pages of long scanned files.

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

Auto reads normal PDFs directly and uses OCR only on pages that have no text (scanned pages). Images always need OCR. OCR pages are billed separately, see Pricing.

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

Languages printed in your scanned documents. Pick 1 or 2; more languages make OCR slower and less accurate.

## `minTextCharsPerPage` (type: `integer`):

In Auto mode, a page with fewer text characters than this is treated as scanned and read with OCR.

## `ocrDpi` (type: `integer`):

Resolution used to render scanned PDF pages for OCR. 200 is a good balance; use 300 for very small print.

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

Password for protected PDFs. The same password is tried on every PDF in the run.

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

Larger files are skipped and reported as errors (no charge). For files over 50 MB, give the run 1 GB of memory or more.

## `maxDocuments` (type: `integer`):

Stop after this many files. 0 means no limit.

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

How many files are downloaded and processed at the same time.

## `respectRobotsTxt` (type: `boolean`):

Skip links that the website's robots.txt asks bots not to fetch. Turn off only for files you own or have permission to download.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.irs.gov/forms-pubs/about-form-w-9"
  ],
  "datasetUrlField": "url",
  "findPdfLinks": true,
  "maxPdfsPerPage": 25,
  "outputFormat": "markdown",
  "includePages": false,
  "chunkSize": 0,
  "chunkOverlap": 200,
  "maxPages": 0,
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "minTextCharsPerPage": 25,
  "ocrDpi": 200,
  "maxFileSizeMb": 50,
  "maxDocuments": 0,
  "maxConcurrency": 2,
  "respectRobotsTxt": true
}
```

# Actor output Schema

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

All rows in the dataset: one per file, with its text (Markdown and/or plain text), metadata, OCR details, optional pages and chunks, and error rows for files that could not be read.

## `overview` (type: `string`):

A compact table with one row per file: status, page and OCR counts, word count, title, author and where the file was found.

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

Totals for the run: documents read, OCR pages, web pages scanned, PDF links found, errors, and whether the run stopped at its maximum cost.

# 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://www.irs.gov/forms-pubs/about-form-w-9"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hydrocore/pdf-image-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://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://www.irs.gov/forms-pubs/about-form-w-9",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("hydrocore/pdf-image-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://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.irs.gov/forms-pubs/about-form-w-9"
  ]
}' |
apify call hydrocore/pdf-image-text-extractor --silent --output-dataset

```

## MCP server setup

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