# PDF Text & Table Extractor: OCR, DOCX, PPTX, XLSX to Data + RAG (`ntriqpro/pdf-text-extractor`) Actor

Extract text from PDF, DOCX, PPTX, HTML, TXT, MD and EML by URL. One document in, hundreds of rows out: typed elements (titles, paragraphs, lists, tables), RAG-ready chunks and a per-document summary. Unofficial wrapper around unstructured (Apache-2.0).

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

## Pricing

Pay per event

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?

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

## PDF Text & Table Extractor — OCR, DOCX, PPTX, XLSX to Structured Data + RAG Chunks

**Turn any document URL into clean, structured data.** Give this Actor one or more public document URLs — **PDF, DOCX, PPTX, XLSX/XLS, CSV/TSV, HTML, TXT, Markdown, EML, or PNG/JPG/TIFF/BMP images** — and it returns a flat dataset of typed elements (titles, paragraphs, list items, tables and **one row per table row**), **RAG-ready text chunks** and one summary row per document with a **one-line verdict**. **Scanned PDFs are OCR'd automatically** (Tesseract) when no text layer is found, and image URLs are read with OCR directly. No manual copy-paste, no file uploads: paste URLs, get rows.

Powered by [unstructured](https://github.com/Unstructured-IO/unstructured) (Apache-2.0), the open-source document-parsing engine with 15,000+ GitHub stars that backs many production RAG pipelines. This is an **unofficial** wrapper; see the disclaimer below.

### What does PDF Text Extractor do?

For every document URL you provide, three kinds of rows are produced:

| Row type | What it is | Typical volume |
|---|---|---|
| `document-summary` | One row per document: `headline` verdict, `verdict` code, page/sheet count, word count, element counts by type, table and table-row counts, languages, strategy used | 1 per document |
| `chunk` | Text chunks sized for embedding into a vector DB — RAG-ready. Four modes: `title` (section-aware), `basic`, `page`, `document` | 10–60 per document |
| `element` | Optional (off by default): one row per structural element — `Title`, `NarrativeText`, `ListItem`, `Table` (with HTML), plus one **`TableRow`** per data row of every table with `header`, `cells` and a `record` object | 100–1,000 per document |

Measured volumes (elements on): a 15-page research paper → **423 rows**; a 700-row spreadsheet → **749 rows** (1 summary + 47 chunks + 1 table + 700 table rows); a 5-table DOCX → **120 rows**.

#### Element types

`Title`, `NarrativeText`, `ListItem`, `Table`, `TableRow`, `UncategorizedText`, `Header`, `Footer`, `Image`, `Address`, `EmailAddress`, `PageBreak` and the other categories unstructured emits. `Table` rows keep their structure as `textAsHtml`; `TableRow` rows hang off their table via `parentId` and carry `rowIndex`, `header`, `cells` and `record` (header → cell). Spreadsheet rows also carry `sheetName`.

#### Verdict layer

Every `document-summary` row includes a `headline` a human can read at a glance and a `verdict` code you can filter on:

| `verdict` | Meaning |
|---|---|
| `structured-text` | Text with 3+ sections (headings) — good RAG material |
| `text` | Plain running text |
| `table-heavy` | Mostly tabular data (spreadsheets, financial tables) — use the `TableRow` elements |
| `scanned-ocr` | No text layer; content came from OCR — check accuracy |
| `sparse` | Fewer than 150 words extracted |
| `empty` | Nothing extractable |

Example: `XLSX, 1 sheet — 1 table, 700 data rows, 12,490 words; 47 RAG chunks (eng)`.

### Why use it?

- **RAG pipelines** — skip the preprocessing step: URLs in, embedding-ready chunks out, on a schedule, via API.
- **Spreadsheets & financial tables** — XLSX/CSV sheets and PDF/DOCX tables become one record per row, ready for a database.
- **Research & analysis** — batch-convert reports, papers and filings into rows you can filter in Excel or a database.
- **Content workflows** — pull structured text out of decks (PPTX), articles (HTML) and docs (DOCX) with one tool instead of five.
- **Apify platform** — run on a schedule, call via API, pipe results into any integration, download as JSON/CSV/Excel.

### How to use it

1. Open the **Input** tab.
2. Paste one or more document URLs (up to 50 per run). Links without a file extension (download links) are fine — the file type is detected from its contents.
3. Optionally pick a parsing strategy and chunking mode, toggle element rows, and set the chunk size.
4. Click **Start**. Download the dataset as JSON, CSV, Excel or HTML, or read it via the API.

### Input example

```json
{
    "documentUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://example.com/report.docx",
        "https://example.com/sales.xlsx"
    ],
    "strategy": "auto",
    "chunkBy": "title",
    "includeChunks": true,
    "includeElements": true,
    "chunkMaxChars": 1800,
    "maxResults": 500
}
```

API callers may send the URL list as `fileUrls` instead of `documentUrls`; both are accepted.

#### Input options

| Field | Values | Notes |
|---|---|---|
| `strategy` | `auto` (default), `fast`, `ocr_only` | `auto` reads the text layer and falls back to OCR for scans; `fast` never OCRs; `ocr_only` forces OCR. `hi_res` layout detection is not bundled (it would download models at run time) and is treated as `auto` with a notice row. |
| `chunkBy` | `title` (default), `basic`, `page`, `document` | `title` never crosses a heading; `page` gives one or more chunks per page (per sheet for spreadsheets); `document` splits only by size. |
| `includeElements` | boolean | Element rows (on by default, high volume). Set `false` for chunks + summaries only. |
| `expandTableRows` | boolean (default true) | With elements on, adds one `TableRow` per data row of every table. |
| `enableOcr`, `ocrLanguages` | boolean, `["eng","kor"]` | OCR settings. |

### Output example

```json
{
    "rowType": "element",
    "sourceUrl": "https://example.com/sales.xlsx",
    "fileType": "xlsx",
    "elementIndex": 2,
    "elementType": "TableRow",
    "text": "2121 | 456 | Jane | 2011 | 842",
    "pageNumber": 1,
    "sheetName": "Sheet1",
    "rowIndex": 1,
    "header": ["Postcode", "Sales_Rep_ID", "Sales_Rep_Name", "Year", "Value"],
    "cells": ["2121", "456", "Jane", "2011", "842"],
    "record": { "Postcode": "2121", "Sales_Rep_ID": "456", "Sales_Rep_Name": "Jane", "Year": "2011", "Value": "842" },
    "parentId": "dde9d41a…"
}
```

### Pricing

Four charge events:

| Event | When it is charged |
|---|---|
| Run start | Once per run, when the run starts |
| Document summary | Per delivered `document-summary` row (one per parsed document) |
| RAG chunk | Per delivered `chunk` row |
| Structured element | Per delivered `element` row, including `TableRow` rows (on by default; off when `includeElements` is `false`) |

Typical run cost (measured on a 15-page research paper):

| Scenario | Rows | What you pay |
|---|---|---|
| 1 paper, default settings (summary + chunks) | ~30 | **≈ $0.32** (1 start + 1 summary + 29 chunks) |
| 1 paper with `includeElements` on | ~423 | **≈ $0.52** (adds 393 element rows) |
| 1 spreadsheet, 700 rows, `includeElements` on | ~749 | **≈ $0.85** (1 start + 1 summary + 47 chunks + 701 element rows) |

Control your spend with:

- `maxResults` — a hard cost ceiling per run (default 500 rows),
- `includeElements` / `includeChunks` / `expandTableRows` — pick the row types you need,
- the run's **maximum charge limit** in Run options.

Fetch status and quality information (HTTP status, page counts, word counts, `verdict`, truncation flags) are provided as data fields so you can judge every row yourself.

**Free plan**: runs parse up to 3 documents and return up to 25 rows per run — prioritised so you get the document summaries and RAG chunks first, then sample elements.

### OCR (scanned PDFs & images)

- **Scanned/image-only PDFs**: when a PDF has no text layer, the Actor automatically re-reads it with OCR (Tesseract, built into the image — nothing downloads at runtime). The document's summary row carries `"ocrApplied": true` and `"verdict": "scanned-ocr"` so you can tell OCR'd text apart.
- **Image URLs** (PNG, JPG, TIFF, BMP) are always read with OCR — paste a receipt or screenshot URL and get its text as rows.
- **Languages**: English (`eng`, default) and Korean (`kor`) — set `"ocrLanguages": ["eng", "kor"]`. Other codes are ignored.
- Turn OCR off entirely with `"enableOcr": false` or `"strategy": "fast"`.
- OCR rows are ordinary result rows — they are charged under the same events as any other row (no OCR surcharge).

### Limitations

- OCR accuracy depends on scan quality; OCR output has no styling-based structure, so most OCR'd text arrives as paragraph elements.
- `hi_res` layout detection (model-based table and figure detection inside scanned PDFs) is not bundled; tables inside scanned PDFs arrive as OCR text.
- Legacy binary formats (`.doc`, `.ppt`) are not supported — convert them to DOCX/PPTX first. Legacy `.xls` is supported.
- Very large tables are expanded to at most 2,000 `TableRow` elements each; the full table is still present as HTML on its `Table` element (truncated at 5,000 characters) and in the chunks.
- Files are fetched by URL (up to 50 MB each); there is no direct file upload.
- Only publicly accessible URLs — documents behind logins cannot be fetched.

### FAQ & disclaimers

**Is this the official unstructured Actor?** No. This is an **unofficial** wrapper around the open-source [unstructured](https://github.com/Unstructured-IO/unstructured) library (Apache-2.0). This Actor is **not affiliated with, endorsed by, or sponsored by Unstructured Technologies, Inc.** All trademarks belong to their respective owners.

**Legal** — you are responsible for having the right to access and process the documents you submit, for complying with each source's Terms of Service and applicable copyright law, and you act as the data controller for any personal data contained in the documents you process.

Found a bug or need a feature? Open an issue on the **Issues** tab — it is checked regularly.

# Actor input Schema

## `documentUrls` (type: `array`):

One or more public URLs of documents to parse. Supported: PDF, DOCX, PPTX, XLSX/XLS, CSV/TSV, HTML, TXT, Markdown, EML, plus PNG/JPG/TIFF/BMP images (read via OCR). Scanned PDFs without a text layer are OCR'd automatically. Each document is broken into typed elements (titles, paragraphs, lists, tables, table rows), RAG-ready chunks and one summary row with a one-line verdict. Max 50 per run. (API callers may also send the same list as "fileUrls".)

## `strategy` (type: `string`):

auto (default): read the text layer, and fall back to OCR for scanned PDFs. fast: text layer only, never OCR (quickest). ocr\_only: force OCR on every PDF and image. hi\_res layout detection is not bundled in this Actor and is treated as auto.

## `chunkBy` (type: `string`):

How RAG chunks are built. title (default): section-aware chunks that never cross a heading. basic: consecutive elements filled up to the size limit. page: one or more chunks per page (per sheet for spreadsheets). document: the whole document split only by size.

## `includeElements` (type: `boolean`):

On by default. Delivers one row per structural element — title, paragraph, list item, table, and one TableRow per data row of every table (spreadsheet sheets become records with a `record` object). This is the detailed, high-volume view of each document (hundreds of rows per document, billed per row). Turn off to receive only RAG chunks and document summaries.

## `expandTableRows` (type: `boolean`):

On by default; only matters when element rows are included. Every table (PDF/DOCX/PPTX/HTML tables, spreadsheet sheets, CSV files) is expanded into one TableRow element per data row with `header`, `cells` and a `record` object. Turn off to receive tables only as single Table elements with HTML.

## `enableOcr` (type: `boolean`):

On by default. Scanned/image-only PDFs (no text layer) are automatically re-read with OCR (Tesseract), and PNG/JPG/TIFF/BMP image URLs are read with OCR. Turn off to skip OCR entirely.

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

Languages Tesseract should read. Available: "eng" (English, default) and "kor" (Korean). Other codes are ignored.

## `includeChunks` (type: `boolean`):

Smart chunks (see Chunking mode), ready to embed into a vector database for RAG pipelines.

## `chunkMaxChars` (type: `integer`):

Upper bound on the size of each RAG chunk. 500-8000.

## `perFileTimeoutSecs` (type: `integer`):

How long a single document may take to parse before it is abandoned. 30-600.

## `maxRunSecs` (type: `integer`):

Stop parsing new documents after this many seconds. Everything already parsed stays in the dataset. 60-21600.

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

Upper bound on billable result rows for this run — your cost ceiling. Free-plan runs return at most 25 rows regardless of this value.

## `disclaimerAck` (type: `boolean`):

Required. By checking this you confirm that (1) you only process publicly accessible documents you are authorized to access; (2) you will respect each source's Terms of Service and applicable copyright; (3) you are responsible for any personal data contained in the documents you process.

## Actor input object example

```json
{
  "documentUrls": [
    "https://arxiv.org/pdf/1706.03762",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "strategy": "auto",
  "chunkBy": "title",
  "includeElements": true,
  "expandTableRows": true,
  "enableOcr": true,
  "ocrLanguages": [
    "eng"
  ],
  "includeChunks": true,
  "chunkMaxChars": 1800,
  "perFileTimeoutSecs": 120,
  "maxRunSecs": 900,
  "maxResults": 500,
  "disclaimerAck": true
}
```

# Actor output Schema

## `dataset` (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 = {
    "documentUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ],
    "strategy": "auto",
    "chunkBy": "title",
    "includeElements": true,
    "expandTableRows": true,
    "enableOcr": true,
    "ocrLanguages": [
        "eng"
    ],
    "includeChunks": true,
    "chunkMaxChars": 1800,
    "perFileTimeoutSecs": 120,
    "maxRunSecs": 900,
    "maxResults": 500,
    "disclaimerAck": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("ntriqpro/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 = {
    "documentUrls": [
        "https://arxiv.org/pdf/1706.03762",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    ],
    "strategy": "auto",
    "chunkBy": "title",
    "includeElements": True,
    "expandTableRows": True,
    "enableOcr": True,
    "ocrLanguages": ["eng"],
    "includeChunks": True,
    "chunkMaxChars": 1800,
    "perFileTimeoutSecs": 120,
    "maxRunSecs": 900,
    "maxResults": 500,
    "disclaimerAck": True,
}

# Run the Actor and wait for it to finish
run = client.actor("ntriqpro/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 '{
  "documentUrls": [
    "https://arxiv.org/pdf/1706.03762",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "strategy": "auto",
  "chunkBy": "title",
  "includeElements": true,
  "expandTableRows": true,
  "enableOcr": true,
  "ocrLanguages": [
    "eng"
  ],
  "includeChunks": true,
  "chunkMaxChars": 1800,
  "perFileTimeoutSecs": 120,
  "maxRunSecs": 900,
  "maxResults": 500,
  "disclaimerAck": true
}' |
apify call ntriqpro/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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