# PDF & Document Text Extractor: PDF, DOCX, PPTX, XLSX to Text (`angadseth/pdf-text-extractor`) Actor

Turn public document URLs (PDF, DOCX, PPTX, XLSX, CSV, HTML, TXT) into clean text: whole document, per page or RAG-ready chunks, with metadata, language, word count, links and approximate tables. Built for AI agents, search indexes and data pipelines. Pay only for successful extractions.

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

## Pricing

$3.00 / 1,000 results

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

## PDF & Document Text Extractor

**Turn links to PDF, Word, PowerPoint, Excel, CSV, HTML and text files into clean text with metadata. Get one record per document, per page, or ready-made chunks for RAG and AI agents.**

Paste document URLs and get the text in reading order: the whole document, page by page, or in overlapping chunks you can send straight to an embedding model. Every record has typed metadata (title, author, dates, page count, language, word count), the links found in the file, and the tables as rows of cells.

### Who uses it

- **AI and RAG builders**: load reports, manuals, papers and policies into a vector database. Chunks mode gives sized, overlapping chunks with page numbers, ready for embedding.
- **AI agents (MCP)**: an agent hands over a URL and gets back clean text it can read, quote and cite by page.
- **Researchers and analysts**: pull text and tables out of hundreds of public reports, filings, tenders or papers in one run.
- **Search and archiving teams**: index document libraries with full text, language and metadata.
- **Data pipelines**: a scheduled run turns new documents into structured JSON, CSV or Excel.

### What it does

| | |
|---|---|
| **Formats** | PDF, DOCX, PPTX (with speaker notes), XLSX (every sheet), CSV/TSV, HTML, TXT. The type is read from the file itself, so links without an extension work. |
| **Output modes** | `documents`: one record per file. `pages`: one record per page (slide for PPTX, sheet for XLSX). `chunks`: overlapping chunks for RAG, with `pageStart`/`pageEnd` and character offsets. |
| **Clean text** | Text in reading order, running headers, footers and page numbers removed, white space tidied, citations and links kept in place. |
| **Metadata** | Title, author, creator app, producer, subject, keywords, last modified by, created and modified dates (ISO 8601), PDF version, form and signature flags, encryption. |
| **Language** | Detected language (ISO 639-1, e.g. `en`) plus the language the file declares, if any. |
| **Counts** | Word count and character count per document, per page and per chunk. |
| **Links** | Every link in the file: PDF link annotations, Word and PowerPoint hyperlinks, HTML anchors (made absolute) and URLs written in the text. |
| **Tables** | Rows of cells. Exact for XLSX, CSV, DOCX, PPTX and HTML; approximate for PDF (found from the layout, marked with `tablesNote`). |
| **Share links** | Google Drive and Dropbox share links are turned into direct downloads. |
| **Protected PDFs** | Add the password in the input. |
| **Fair billing** | Only successful extractions are saved and charged. Failed URLs cost nothing and are listed with the reason in the `SUMMARY` record. |

### Input examples

The default input reads one 12-page RFC PDF in a few seconds:

```json
{
  "urls": ["https://www.rfc-editor.org/rfc/rfc9309.pdf"]
}
```

Several formats, one record per page:

```json
{
  "urls": [
    "https://www.rfc-editor.org/rfc/rfc9309.pdf",
    "https://calibre-ebook.com/downloads/demos/demo.docx",
    "https://www.rfc-editor.org/rfc/rfc9309.html"
  ],
  "outputMode": "pages"
}
```

RAG chunks of about 1,500 characters (about 375 tokens) with 200 characters of overlap:

```json
{
  "urls": ["https://www.rfc-editor.org/rfc/rfc9309.pdf"],
  "outputMode": "chunks",
  "chunkSize": 1500,
  "chunkOverlap": 200
}
```

Smaller records: metadata and full text only, no per-page array or tables:

```json
{
  "urls": ["https://www.rfc-editor.org/rfc/rfc9309.pdf"],
  "includePages": false,
  "extractTables": false
}
```

### Output example

A real record from the default input (`documents` mode; the long `text`, `pages`, `links` and `tables` are shortened here):

```json
{
  "url": "https://www.rfc-editor.org/rfc/rfc9309.pdf",
  "finalUrl": "https://www.rfc-editor.org/rfc/rfc9309.pdf",
  "fileName": "rfc9309.pdf",
  "documentType": "pdf",
  "mimeType": "application/pdf",
  "fileSizeBytes": 177688,
  "title": "RFC 9309: Robots Exclusion Protocol",
  "pageCount": 12,
  "pagesExtracted": 12,
  "pageUnit": "page",
  "author": "Martijn Koster, Gary Illyes, Henner Zeller, Lizzi Sassman",
  "creator": "xml2rfc 3.14.2",
  "producer": "WeasyPrint 56.1",
  "subject": "This document specifies and extends the \"Robots Exclusion Protocol\" ...",
  "keywords": null,
  "lastModifiedBy": null,
  "createdAt": "2022-09-10T00:39:45.000Z",
  "modifiedAt": "2022-09-10T00:39:47.000Z",
  "isEncrypted": false,
  "pdfVersion": "1.7",
  "hasForm": false,
  "isSigned": false,
  "declaredLanguage": null,
  "language": "en",
  "languageIso3": "eng",
  "wordCount": 3042,
  "charCount": 21129,
  "linkCount": 26,
  "tableCount": 4,
  "text": "RFC 9309\nRobots Exclusion Protocol\n\nAbstract\nThis document specifies and extends the \"Robots Exclusion Protocol\" method originally defined ...",
  "pages": [
    { "pageNumber": 1, "text": "RFC 9309\nRobots Exclusion Protocol\n\nAbstract ...", "wordCount": 284, "charCount": 1839 },
    { "pageNumber": 3, "text": "1. Introduction\nThis document applies to services that provide resources that clients can access through URIs as\ndefined in [RFC3986]. For example, ...", "wordCount": 269, "charCount": 1640 }
  ],
  "links": ["https://www.rfc-editor.org/rfc/rfc9309", "https://www.rfc-editor.org/info/rfc9309", "https://trustee.ietf.org/license-info"],
  "tables": [
    { "page": 1, "rowCount": 7, "columnCount": 5, "rows": [["Stream:", "Internet Engineering Task Force (IETF)"], ["RFC:", "9309"], ["Category:", "Standards Track"]] }
  ],
  "tablesNote": "PDF tables are found from the page layout (best effort). Check them before you rely on them; merged cells and multi-line cells may split or join.",
  "warnings": [],
  "extractedAt": "2026-11-05T16:39:06.678Z"
}
```

A real record in `chunks` mode (RFC 9309 as plain text, `chunkSize` 800, `chunkOverlap` 150):

```json
{
  "url": "https://www.rfc-editor.org/rfc/rfc9309.txt",
  "fileName": "rfc9309.txt",
  "documentType": "txt",
  "chunkId": "b81919b50d6942b0-1",
  "chunkIndex": 1,
  "chunkCount": 36,
  "pageStart": 1,
  "pageEnd": 1,
  "charStart": 528,
  "charEnd": 1269,
  "text": "instructions for handling errors, and instructions for caching.\n\nStatus of This Memo\n\nThis is an Internet Standards Track document.\n\nThis document is a product of the Internet Engineering Task Force\n(IETF). ...",
  "charCount": 741,
  "wordCount": 115,
  "pageCount": 1,
  "pageUnit": "document",
  "language": "en",
  "author": null,
  "createdAt": null,
  "extractedAt": "2026-11-05T16:39:00.011Z"
}
```

`pageUnit` tells you what a "page" is: `page` for PDF (and TXT files with form feeds), `slide` for PPTX, `sheet` for XLSX, and `document` for DOCX, HTML, CSV and plain TXT, which have no fixed pages. `chunkId` is stable for the same URL and settings, so re-runs can update a vector store instead of adding duplicates.

The `SUMMARY` record in the key-value store lists every URL with `ok` or `failed`, the reason for each failure, records saved, word counts and warnings.

### Pricing

**$0.003 per result.** A result is one record in the dataset, so the count depends on the mode:

| Mode | You pay for | 12-page PDF costs |
|---|---|---|
| `documents` | 1 result per document | $0.003 |
| `pages` | 1 result per page with text | $0.036 |
| `chunks` | 1 result per chunk | about $0.05 at 1,500 characters |

Failed URLs (404, wrong type, no text, too large) are not saved and not charged. Use **Max results** to cap the cost of a run. Apify platform usage for the run is billed by Apify as usual.

### Limits

- **No OCR.** The Actor reads the text layer of a PDF. A scanned PDF without a text layer gives little or no text; the record then carries a warning, and a PDF with no text at all fails (free) with a clear message. Image files are not read.
- PDF tables are approximate, found from where the text sits on the page. Tables from XLSX, CSV, DOCX, PPTX and HTML are exact.
- Text from multi-column PDFs follows the order the PDF stores it in, which is usually the reading order but not always.
- DOCX, HTML and plain TXT have no fixed pages, so they come back as one page; `pageCount` for DOCX is the page count Word saved, when present.
- Legacy `.doc`, `.xls`, `.ppt`, OpenDocument files and ZIP archives are not supported; the reason is listed in `SUMMARY`.
- Only public links. Files behind a login, a paywall or bot protection cannot be read. JavaScript-built web pages give only the HTML the server sends.
- Default size limit 50 MB per file (up to 500 MB with more memory).

### FAQ

**Is this legal?** The Actor downloads only the URLs you give it, once each, the same way a browser does. It does not log in, solve captchas or get around paywalls or bot protection. Make sure you have the right to use the documents you process, as you would with any file you download.

**Which output mode should I pick?** `documents` to keep files whole with all metadata, `pages` to search or cite by page, `chunks` to fill a vector database for RAG.

**How big should chunks be?** 1,000 to 2,000 characters with 10 to 20 percent overlap works well for most embedding models. Chunks end at a paragraph, sentence or word, and the overlap starts at a sentence when one is close.

**Why is a PDF's text empty or short?** It is probably a scanned image without a text layer. This Actor does not run OCR.

**Can I send hundreds of URLs?** Yes. Documents are read a few at a time (set **Documents at a time**), each download is retried on network errors, 429 and 5xx answers, and one bad URL never stops the run.

### Integrations

- **Schedules**: run daily or weekly on a list of report URLs.
- **Webhooks**: call your endpoint when a run finishes, then pull the dataset.
- **Google Sheets, Make, Zapier, n8n**: send records to a sheet or a workflow with Apify's ready-made integrations.
- **API and SDKs**: start runs and read results from Python, JavaScript or plain HTTP.
- **MCP for AI agents**: give an agent this Actor through Apify's MCP server so it can read any document link on its own.
- **Vector databases**: chunks mode output maps straight onto Pinecone, Qdrant, Weaviate or pgvector rows (`chunkId`, `text`, plus the metadata fields).

### More Actors by angadseth

- [GeM Tenders Scraper (India Government e-Marketplace Bids)](https://apify.com/angadseth/gem-tenders-scraper): Get live GeM bids and reverse auctions from India's Government e-Marketplace
- [India eProcurement Tenders Scraper (CPPP + 30 State Portals)](https://apify.com/angadseth/india-eprocurement-tenders): Live government tenders from India's central eProcure (CPPP), central PSU, defence and 30+ state NIC eProcurement portals in one run.
- [Apple App Store Scraper: apps, ratings and reviews](https://apify.com/angadseth/app-store-scraper): Get Apple App Store apps, ratings and customer reviews from many countries in one run
- [ATS Jobs Feed: Greenhouse, Lever, Ashby, Workday + 5 more](https://apify.com/angadseth/ats-jobs-feed): Get every open job from a company's own careers board
- [Bluesky Scraper: Posts, Profiles, Followers and Feeds](https://apify.com/angadseth/bluesky-scraper): Scrape Bluesky without a login
- [Website Contact Enricher: Emails, Phones, Socials per Domain](https://apify.com/angadseth/website-contact-enricher): One clean lead row per company domain
- [Website Screenshot & PDF: full page, mobile, element, WebP](https://apify.com/angadseth/website-screenshot): Screenshots (PNG, JPEG, WebP) and PDFs of any list of URLs.
- [Remote Jobs Aggregator: RemoteOK, Jobicy, Arbeitnow + 2 more](https://apify.com/angadseth/remote-jobs-aggregator): Remote jobs from RemoteOK, Jobicy, Arbeitnow, Working Nomads and 4 Day Week in one clean feed.

# Actor input Schema

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

One public link per line to a PDF, DOCX, PPTX, XLSX, CSV, HTML or TXT file. The type is detected from the file itself, so links without an extension work too. Google Drive and Dropbox share links are turned into direct downloads. Files behind a login cannot be read.

## `outputMode` (type: `string`):

Documents = one record per file with full text, per-page text, metadata, links and tables. Pages = one record per page (slide for PPTX, sheet for XLSX). Chunks = overlapping text chunks sized for embeddings, RAG and AI agents. You pay per record, so the mode decides the count.

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

Chunks mode only. Target length of each chunk in characters (about 4 characters per token). Chunks end at a paragraph, line, sentence or word boundary.

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

Chunks mode only. How many characters each chunk repeats from the end of the previous one, so context is not lost at the cut. At most half the chunk size.

## `maxResults` (type: `integer`):

Stop after this many records in total (documents, pages or chunks, depending on Output mode). You pay per record saved.

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

Read only the first N pages of each PDF, e.g. to sample long reports. 0 = all pages.

## `includeFullText` (type: `boolean`):

Documents mode: add the whole document text in one field.

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

Documents mode: add a pages array with the text and word count of each page. Turn off for smaller records.

## `extractTables` (type: `boolean`):

Add tables as rows of cells. Exact for XLSX, CSV, DOCX, PPTX and HTML; approximate for PDF (found from the text layout).

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

Drop lines that repeat at the top or bottom of most pages (running titles, page numbers), so the text reads cleanly.

## `includeSpeakerNotes` (type: `boolean`):

Add the speaker notes of each slide after its text.

## `maxTableRows` (type: `integer`):

Read at most this many rows from each XLSX sheet or CSV file.

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

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

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

Skip files larger than this. Large PDFs need more memory.

## `requestTimeoutSecs` (type: `integer`):

Give up on a download after this many seconds. Network errors, 429 and 5xx answers are retried up to 3 times.

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

How many documents to download and read in parallel.

## `saveFailuresToDataset` (type: `boolean`):

Also list failed URLs with the reason in a named dataset called pdf-text-extractor-failures. Failures never go to the main dataset and are never charged; they always appear in the SUMMARY record.

## Actor input object example

```json
{
  "urls": [
    "https://www.rfc-editor.org/rfc/rfc9309.pdf",
    "https://www.rfc-editor.org/rfc/rfc9309.txt"
  ],
  "outputMode": "documents",
  "chunkSize": 1500,
  "chunkOverlap": 200,
  "maxResults": 1000,
  "maxPagesPerDocument": 0,
  "includeFullText": true,
  "includePages": true,
  "extractTables": true,
  "removeHeadersFooters": true,
  "includeSpeakerNotes": true,
  "maxTableRows": 5000,
  "maxFileSizeMb": 50,
  "requestTimeoutSecs": 60,
  "maxConcurrency": 3,
  "saveFailuresToDataset": false
}
```

# Actor output Schema

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

One record per document, page or chunk (depending on Output mode), as a dataset you can download in JSON, CSV or Excel.

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

Status of every URL (ok, or failed with the reason), records per document, word counts and warnings from this run.

# 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.rfc-editor.org/rfc/rfc9309.pdf",
        "https://www.rfc-editor.org/rfc/rfc9309.txt"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("angadseth/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://www.rfc-editor.org/rfc/rfc9309.pdf",
        "https://www.rfc-editor.org/rfc/rfc9309.txt",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("angadseth/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://www.rfc-editor.org/rfc/rfc9309.pdf",
    "https://www.rfc-editor.org/rfc/rfc9309.txt"
  ]
}' |
apify call angadseth/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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