# PDF Page Text Extractor – No Charge for Blank Pages (`rowfeed/pdf-page-text-extractor`) Actor

Extract text from any PDF URL, one row per page. Pages with no extractable text (scanned PDFs) are never charged — the main differentiator. pypdf parsing, no OCR. Clean JSON for RAG pipelines, researchers and automation.

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

## Pricing

$0.50 / 1,000 pages

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?

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

Turn any **PDF URL** into clean, structured text: **one row per page**, ready for search, RAG ingestion, or a spreadsheet. Give it a list of PDF links — invoices, forms, reports, research papers, anything public on the web — and get back plain text per page in seconds, no login and no browser. Built for **RAG/LLM ingestion pipelines** that need page-addressable chunks instead of one giant text blob, **researchers and analysts** who need to search or diff text across a batch of PDFs, and **automation/no-code workflows** (n8n, Make, Zapier) that need a PDF-to-text step without running their own parsing library.

This Actor never scrapes a website — you bring your own PDF URLs. There is no source to break, no ToS to worry about, and no proxy involved: just an HTTP fetch and a text extraction.

### What you get

- **One row per page**, not one blob per document — `page` number, `text`, `char_count`, the document's total `page_count` and `title`, so downstream chunking and citation ("page 4 of invoice.pdf") is trivial.
- **A page-range filter** (`firstPage`/`lastPage`) plus a per-PDF page cap (`maxPagesPerPdf`), so a 400-page report can be sampled or split into predictable runs.
- **Errors instead of crashes** — a bad URL, a 404, a non-PDF response or a password-protected PDF becomes a single unbilled error row (`error`, `errorMessage`), and the rest of your list keeps going.

### You are never charged for a page with no extractable text

This is the main way this Actor differs from other PDF-to-text tools on Apify Store: **a page only becomes a row, and only then costs anything, if `pypdf` actually pulled text out of it.** A scanned or image-only PDF — a photographed contract, a faxed form, a book cover-to-cover as images — has zero extractable text on every page, so it produces **zero charged rows**. You pay nothing for it. The page is still counted (and logged) in the run's `STATS` record so you can see it happened, but it never reaches the dataset and never reaches your bill.

**This Actor does not do OCR.** It extracts the text layer that is already embedded in the PDF (the same text you could select and copy in a PDF viewer). If a page is a picture of text with no underlying text layer, no tool that isn't running OCR can read it — including this one — and per the rule above, you are not charged for trying.

### Sample row

A real row from a default run against `fw9.pdf` (IRS Form W-9):

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "page": 1,
  "text": "Form  W-9\n(Rev. March 2024)\nRequest for Taxpayer \nIdentification Number and Certification\nDepartment...",
  "char_count": 5727,
  "page_count": 6,
  "title": "Form W-9 (Rev. March 2024)",
  "scraped_at": "2026-09-11T01:34:09+00:00"
}
```

### Filters

| Input | Default | What it does |
|---|---|---|
| `pdfUrls` | IRS Form W-9 + Form 1040 | Direct http/https links to PDF files. |
| `maxPagesPerPdf` | `50` | Stop after this many pages per PDF (from the start of the resolved range). |
| `firstPage` | `0` (unset) | First page to extract, 1-based. |
| `lastPage` | `0` (unset) | Last page to extract, 1-based. |
| `includeDocumentInfo` | `true` | Add the PDF's `title` to every row. |

### No personal data, ever

The row only ever carries `title` from the PDF's own metadata. `author`, `creator`, `producer` and every other metadata field that can hold a person's name are **never** read into a row, whether `includeDocumentInfo` is on or off. What you get is the document's text and page structure — nothing that identifies who wrote or produced the file.

### Pricing

Pay per event, no subscription: **$0.50 per 1,000 pages**. Only pages with extractable text are billed — see above. A default run (2 PDFs, 8 pages total, all with text) costs under half a cent. There is no charge for error rows, and no charge for pages with no extractable text. If every URL in your list fails (bad URL, not a PDF, encrypted), the run is marked failed rather than quietly returning nothing, and you are billed for nothing.

### Use it from your tools

- **API and SDKs** — call it via the Apify API or the official Python/JavaScript clients: one call to start the run, one to fetch the page-text rows as JSON, CSV or Excel.
- **n8n, Make and Zapier** — trigger runs and feed extracted page text into a workflow through Apify's integration for each.
- **AI agents and MCP** — eligible for agentic use via Apify's MCP server with pay-per-event pricing, so an agent can hand it a PDF URL mid-task and pay only for the pages it actually got text from.

### Details

- **Fetching**: plain `httpx` GET over http/https only (no proxy, no browser). Requests are capped at 50 MB and time out after 30 s; 429 and 5xx responses are retried with exponential backoff (5 tries), 404/401/403 are not.
- **Not a PDF**: a response that doesn't start with a PDF file header becomes an unbilled `not_pdf` error row instead of being handed to the parser.
- **Encrypted PDFs**: a password-protected PDF that can't be opened with an empty password becomes an unbilled `encrypted` error row.
- **No OCR**: only the PDF's existing text layer is read. Scanned/image-only pages extract as empty text and are never charged (see above).
- **Run stats**: the `STATS` record in the run's key-value store holds per-PDF and per-page counts, including pages skipped for having no extractable text.
- **Not affiliated** with any PDF source you point it at — you supply your own URLs; nothing is scraped from a website.

# Actor input Schema

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

Direct http/https links to PDF files. Each PDF is fetched (max 50 MB, 30 s timeout) and split into one row per page. A bad URL, a non-PDF response or an encrypted PDF becomes an unbilled error row instead of stopping the run.

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

Stop after this many pages per PDF (from the start of the resolved range). Each page with extractable text is one `page` event ($0.50 per 1,000).

## `firstPage` (type: `integer`):

First page to extract, 1-based. 0 = start from page 1.

## `lastPage` (type: `integer`):

Last page to extract, 1-based. 0 = extract through the last page (subject to maxPagesPerPdf).

## `includeDocumentInfo` (type: `boolean`):

Add the PDF's title (from its metadata) to every row. Author, creator and other metadata fields that can name a person are never extracted, on or off.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ],
  "maxPagesPerPdf": 50,
  "firstPage": 0,
  "lastPage": 0,
  "includeDocumentInfo": true
}
```

# Actor output Schema

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

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("rowfeed/pdf-page-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("rowfeed/pdf-page-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 '{}' |
apify call rowfeed/pdf-page-text-extractor --silent --output-dataset

```

## MCP server setup

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