# PDF Text Extractor - PDF to Text, Markdown & Scanned PDF OCR (`gochujang/pdf-text-extractor`) Actor

Extract text from PDF URLs or base64 files with this PDF extractor and PDF parser. Get per-page text, PDF to Markdown, or RAG chunks; optionally extract tables and OCR scanned pages.

- **URL**: https://apify.com/gochujang/pdf-text-extractor.md
- **Developed by:** [Hojun Lee](https://apify.com/gochujang) (community)
- **Categories:** AI, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 290 total users, 38 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 pdf extracteds

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 to Text Extractor — PDF Parser, PDF to Markdown, RAG Chunks

Extract text from PDF URLs or base64 files with a PDF extractor that returns page text, PDF to Markdown, or RAG chunks. Enable OCR for scanned pages; native-text PDFs work without it.

### Quick start

This public one-page PDF finishes in a few seconds:

```json
{"urls":["https://pdfobject.com/pdf/sample.pdf"],"outputFormat":"per_page","maxPages":1}
```

One dataset item from this input (text truncated here; a `summary` row per PDF follows the page rows):

```json
{"_type": "page", "url": "https://pdfobject.com/pdf/sample.pdf", "url_index": 0, "page": 1, "page_count": 1, "char_count": 2878, "word_count": 416, "text": "Sample PDF\nThis is a simple PDF file. Fun fun fun.\nLorem ipsum dolor sit amet, ...", "table_count": 0, "ocr_applied": false}
```

`_type` identifies the record as a page. `page` and `page_count` locate it in the PDF. `text` is extracted text; `char_count` and `word_count` describe it. `table_count` counts detected tables, and `ocr_applied` reports whether OCR was used. Text layout and counts can vary slightly by parser version.

### Bank & card statement ledger (beta)

Set `statementLedger: true` to add a `ledger` object to extracted PDF rows. The actor first reads detected tables, then tries text lines when no table rows are recognized. Rows include date, description, debit, credit, signed amount, and balance; `balance_check` counts consecutive balance mismatches, and `confidence` is `high`, `medium`, or `low`. Missing or ambiguous fields remain null or raw. This is a bank-agnostic beta, not a claim of support for any particular bank or card format. The flag is off by default and adds no billing event.

### Limits

The URL must return a readable PDF. Password-protected files need the `password` input; wrong passwords, corrupt files, oversized files, and blocked downloads produce errors. Scanned pages need `enableOcr: true` for text recognition. `maxPages` defaults to 100 and `maxPdfMb` to 50.

### Pricing

Pay-per-event — you pay only for what is extracted:

| Event | Price |
|---|---|
| Actor start (charged by Apify, one per GB of run memory) | $0.001 |
| PDF successfully extracted | $0.02 |
| Page extracted | $0.001 |

Not charged: failed PDFs (download error, wrong/missing password, corrupt file — you get an `ok: false` row), pages that fail or time out, and pages dropped by `skipEmptyPages`. OCR and output format add no fee: each physical page is billed once in `per_page`, `combined`, `both`, `markdown` or `rag_chunks`.

Example: one 80-page native-text PDF at 1 GB = $0.001 + $0.02 + 80 × $0.001 = **$0.101**.

### Use from AI agents (MCP)

Connect your agent to [Apify's official MCP server](https://mcp.apify.com) with an Apify token. Select the Actor tool `gochujang/pdf-text-extractor`, pass the Quick start JSON as input, then retrieve its dataset items through the MCP server. No separate gateway is needed.

***

> ⭐ **If this saves you time, a quick [review on the Store page](https://apify.com/gochujang/pdf-text-extractor#reviews) helps others find it.**

***

### ⚡ Run in 30 seconds

Paste any PDF URL — direct links, Google Drive, or Dropbox — and click **Start**:

```
## Direct PDF URL
https://www.irs.gov/pub/irs-pdf/f1040.pdf

## Google Drive share link (auto-resolved)
https://drive.google.com/file/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/view

## Dropbox share link (auto-resolved)
https://www.dropbox.com/s/abc123/report.pdf?dl=0

## SharePoint / OneDrive share link (auto-resolved)
https://company.sharepoint.com/:b:/s/team/EaBcD...view.aspx
```

Default output is rich **Markdown** — headings, lists, tables, code blocks auto-detected. Switch to `rag_chunks` mode to get overlapping text windows ready to embed into **Pinecone, Weaviate, Chroma, Qdrant, or any vector DB**.

***

### Why this PDF scraper?

Free PDF extractors give you raw text dumps. This actor goes further:

| Feature | Free alternatives | This actor |
|---|---|---|
| RAG chunks (configurable overlap) | ❌ | ✅ |
| SharePoint / OneDrive URL resolution | ❌ | ✅ |
| Table extraction (2D arrays) | ❌ | ✅ |
| PDF type classification (text vs scanned) | ❌ | ✅ |
| Password-protected PDFs | ❌ | ✅ |
| Auto-OCR for scanned pages | Partial | ✅ Full |
| Skip empty pages option | ❌ | ✅ |
| Batch 50 PDFs in one run | Partial | ✅ |
| Page range selection | ❌ | ✅ |
| Webhook / Telegram alerts | ❌ | ✅ |

***

### Python API example

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

## Extract text from a single PDF
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "url": "https://www.sec.gov/Archives/edgar/data/320193/aapl-10k.pdf",
    "outputFormat": "markdown",
})

items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
markdown_doc = next(i for i in items if i["_type"] == "markdown")
print(markdown_doc["markdown"][:500])
```

```python
## Batch: extract tables from 10 SEC filings at once
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "urls": [
        "https://www.sec.gov/Archives/edgar/data/320193/aapl-10k.pdf",
        "https://www.sec.gov/Archives/edgar/data/789019/msft-10k.pdf",
    ],
    "extractTables": True,
    "outputFormat": "per_page",
    "maxPages": 200,
})
pages = list(client.dataset(run["defaultDatasetId"]).iterate_items())
```

```python
## RAG pipeline: extract + chunk + embed
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "url": "https://example.com/whitepaper.pdf",
    "outputFormat": "rag_chunks",
    "chunkSize": 1000,
    "chunkOverlap": 200,
})
chunks = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items()
          if i["_type"] == "chunk"]
## chunks are ready to embed into Pinecone/Weaviate/Chroma
```

***

### Use cases

1. **AI / LLM pipelines** — Convert PDF to text for ChatGPT, Claude, Gemini, or RAG vector search. One call returns clean chunks ready for embedding.
2. **SEC filings** — Pull text + tables from 10-K, 10-Q, 8-K in batch. Combine with our [SEC EDGAR Tracker](https://apify.com/gochujang/sec-edgar-tracker).
3. **Research aggregation** — Build a searchable database of academic papers + abstracts from arXiv or PubMed PDFs.
4. **Financial reports** — Auto-extract earnings tables from quarterly releases; pipe into spreadsheets or databases.
5. **Legal documents** — Contracts, court filings, regulatory submissions — extract and index at scale.
6. **Invoice & receipt parsing** — Extract line items and totals from PDF invoices without manual data entry.
7. **Government records** — FOIA responses, regulatory filings, public data PDFs — download and parse in bulk.
8. **Scanned document digitization** — Bank statements, old reports, image PDFs via OCR. Enable `enableOcr` and get text even from image-only PDFs.
9. **Compliance audit** — Index every PDF in your corporate document store; feed into your search or compliance tools.

***

### What you get

#### Summary row (one per PDF)

```json
{
  "_type": "summary",
  "url": "https://www.sec.gov/Archives/.../aapl-10k.pdf",
  "ok": true,
  "page_count": 80,
  "pages_extracted": 80,
  "title": "Apple Inc. — Annual Report 2024",
  "author": "Apple Inc.",
  "created": "D:20240928081300Z",
  "text_preview": "UNITED STATES SECURITIES AND EXCHANGE COMMISSION...",
  "file_size_kb": 4820
}
```

#### Per-page row

```json
{
  "_type": "page",
  "url": "https://...",
  "page": 12,
  "page_count": 80,
  "char_count": 3210,
  "word_count": 524,
  "text": "Item 1A. Risk Factors\n\nOur business...",
  "tables": [
    [
      ["Revenue", "Q1 2024", "Q4 2023"],
      ["iPhone", "$45.96B", "$43.81B"],
      ["Mac", "$9.66B", "$7.61B"]
    ]
  ],
  "table_count": 1,
  "ocr_applied": false
}
```

***

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `urls` | array | — | List of PDF URLs to extract (batch mode) |
| `urlsText` | string | — | Paste multiple PDF URLs, one per line — merged with `urls` |
| `url` | string | — | Single PDF URL (used when `urls` is empty) |
| `changeDetection` | boolean | `false` | Hash each PDF's text and compare to the previous run — adds `changed` + `content_hash` to every record. Ideal for scheduled monitoring |
| `webhookUrl` | string | — | POST a JSON alert to this URL when a PDF changes (requires `changeDetection=true`). Works with Slack, Discord, Make, Zapier, n8n |
| `changedOnly` | boolean | `false` | Only output records for PDFs that changed (requires `changeDetection=true`). Scheduled runs produce zero records when nothing changed |
| `outputFormat` | string | `markdown` | `markdown` (LLM-ready), `per_page`, `combined`, `both`, or `rag_chunks` |
| `extractTables` | boolean | `false` | Detect and extract tables as 2D arrays |
| `statementLedger` | boolean | `false` | Parse a bank or card statement into a best-effort transaction ledger (beta) |
| `extractImages` | boolean | `false` | Add `image_count` field to each page |
| `skipEmptyPages` | boolean | `false` | Skip pages with no extractable text (useful for scanned PDFs with mixed pages) |
| `enableOcr` | boolean | `false` | Enable OCR for scanned pages (pages with < 50 chars auto-detected) |
| `ocrLanguage` | string | `eng` | Tesseract language: `eng`, `deu`, `fra`, `jpn`, or combined `eng+deu` |
| `startPage` | integer | 1 | First page to extract (1-indexed) |
| `endPage` | integer | — | Last page to extract (inclusive) |
| `maxPages` | integer | 100 | Max pages per PDF from `startPage` |
| `password` | string | — | Password for password-protected / encrypted PDFs |
| `userAgent` | string | — | Custom User-Agent for the HTTP request |
| `proxyConfiguration` | object | — | Apify Proxy settings. Some corporate CDNs (Akamai/Cloudflare bot management) block datacenter IPs with HTTP 403 even with a browser User-Agent — enable proxy (residential recommended) if a PDF fails to download with a 403 |

***

### Input examples

#### Single PDF

```json
{
  "url": "https://www.example.com/whitepaper.pdf"
}
```

#### Batch of 10-K filings from SEC

```json
{
  "urls": [
    "https://www.sec.gov/Archives/edgar/data/320193/aapl-10k.pdf",
    "https://www.sec.gov/Archives/edgar/data/789019/msft-10k.pdf"
  ],
  "extractTables": true,
  "maxPages": 200
}
```

#### Text-only (skip tables for speed)

```json
{
  "url": "https://...",
  "extractTables": false
}
```

#### Skip blank/empty pages

```json
{
  "url": "https://example.com/mixed-scanned.pdf",
  "skipEmptyPages": true,
  "enableOcr": true
}
```

***

### Monitor PDF Changes Automatically

Turn this actor into a zero-maintenance document monitor in three steps:

1. Enable `changeDetection: true`
2. Set `webhookUrl` to your Slack / Make / Zapier / n8n webhook
3. Schedule to run daily at 8am

You get notified the moment your document updates — and zero records when nothing changed.

```json
{
  "urls": ["https://example.com/policy.pdf"],
  "changeDetection": true,
  "changedOnly": true,
  "webhookUrl": "https://hooks.slack.com/services/T.../B.../..."
}
```

***

### Scheduling & change detection (monitor a PDF for updates)

Set `changeDetection: true` to turn this actor into a **document monitor**. On every run it hashes the extracted text of each PDF, stores the hash in the Actor's Key-Value Store, and compares it against the previous run. Every output record gains two fields:

- `changed` — `true` if the PDF's text differs from the last run, `false` if identical, `null` if detection is disabled or the fetch failed. **The first run is always `changed: true`** (no prior hash to compare).
- `content_hash` — the MD5 of the extracted text, so you can diff or dedupe downstream.

This lets you [schedule the actor](https://docs.apify.com/platform/schedules) (e.g. weekly) and only act when something actually changes — trigger a webhook, re-index a vector store, or send an alert.

#### Example: monitor a regulatory PDF weekly

```json
{
  "urls": [
    "https://www.sec.gov/files/rules/final/2024/33-11275.pdf"
  ],
  "changeDetection": true,
  "outputFormat": "markdown"
}
```

Then schedule it with a weekly cron (`0 9 * * 1`). Downstream, only react to records where `changed` is `true`:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "urls": ["https://www.sec.gov/files/rules/final/2024/33-11275.pdf"],
    "changeDetection": True,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("_type") == "summary" and item.get("changed"):
        print(f"PDF changed! {item['url']} (hash {item['content_hash'][:8]})")
        # → fire your webhook / re-embed / notify here
```

> Change detection persists hashes in the Actor's **named Key-Value Store**, keyed per URL. Runs of the same actor share that store, so scheduled runs compare against each other automatically — no extra setup.

***

### Webhook alerts (Slack / Discord / Make / Zapier / n8n)

Add `webhookUrl` to get an instant HTTP notification whenever a PDF changes. Works with any service that accepts POST JSON.

```json
{
  "urls": ["https://www.sec.gov/files/rules/final/2024/33-11275.pdf"],
  "changeDetection": true,
  "webhookUrl": "https://hooks.slack.com/services/T.../B.../...",
  "changedOnly": true
}
```

**Webhook payload** (POST, `application/json`):

```json
{
  "event": "pdf_changed",
  "url": "https://www.sec.gov/files/rules/final/2024/33-11275.pdf",
  "content_hash": "a3f8c2...",
  "page_count": 42,
  "word_count": 18340,
  "title": "Final Rule Release No. 33-11275",
  "detected_at": "2024-10-15T09:00:12Z"
}
```

#### changedOnly mode

Set `changedOnly: true` (requires `changeDetection: true`) to suppress all output for unchanged PDFs. Your scheduled dataset is empty when nothing changed — no downstream filtering needed.

```json
{
  "urls": [
    "https://example.com/report-q3.pdf",
    "https://example.com/policy-v2.pdf"
  ],
  "changeDetection": true,
  "changedOnly": true,
  "outputFormat": "markdown"
}
```

If neither PDF changed since the last run, the dataset is empty. Only the changed one produces records.

***

### RAG-Ready Chunking (LangChain / LlamaIndex / Vector DB)

Use `outputFormat: "rag_chunks"` to get overlapping text chunks ready to embed into Pinecone, Weaviate, Chroma, Qdrant, or any vector database:

```json
{
  "url": "https://example.com/whitepaper.pdf",
  "outputFormat": "rag_chunks",
  "chunkSize": 1000,
  "chunkOverlap": 200
}
```

Each chunk row includes context metadata for retrieval:

```json
{
  "_type": "chunk",
  "chunk_index": 3,
  "total_chunks": 47,
  "text": "...overlapping window of text...",
  "doc_title": "Whitepaper Title",
  "doc_author": "Author Name",
  "doc_page_count": 24,
  "pdf_type": "text_based"
}
```

#### LangChain integration

```python
from apify_client import ApifyClient
from langchain.schema import Document
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "urls": ["https://example.com/report.pdf"],
    "outputFormat": "rag_chunks",
    "chunkSize": 1000,
    "chunkOverlap": 200,
})

chunks = list(client.dataset(run["defaultDatasetId"]).iterate_items())
docs = [Document(page_content=c["text"], metadata={"title": c["doc_title"], "chunk": c["chunk_index"]})
        for c in chunks if c.get("_type") == "chunk"]

vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())
```

#### LlamaIndex integration

```python
from apify_client import ApifyClient
from llama_index.core import VectorStoreIndex
from llama_index.core.schema import TextNode

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("gochujang/pdf-text-extractor").call(run_input={
    "urls": ["https://example.com/report.pdf"],
    "outputFormat": "rag_chunks",
})

chunks = list(client.dataset(run["defaultDatasetId"]).iterate_items())
nodes = [TextNode(text=c["text"], metadata={"title": c.get("doc_title",""), "chunk_index": c.get("chunk_index",0)})
         for c in chunks if c.get("_type") == "chunk"]

index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine()
```

No post-processing needed — chunks are word-boundary aligned, non-empty, and sized for typical embedding models (512–2048 tokens).

***

### OCR for Scanned PDFs

Enable `enableOcr: true` to handle scanned or image-based PDFs (bank statements, legal filings, old reports). The actor automatically detects pages with little extractable text and falls back to Tesseract OCR.

```json
{
  "url": "https://example.com/scanned-report.pdf",
  "enableOcr": true,
  "ocrLanguage": "eng"
}
```

Each page row includes `"ocr_applied": true` when OCR was used, so you can distinguish native-text pages from OCR-processed ones.

**Supported languages**: `eng` (English), `deu` (German), `fra` (French), `spa` (Spanish), `jpn` (Japanese), and [all Tesseract languages](https://github.com/tesseract-ocr/tessdata). Multi-language: `"eng+deu"`.

***

### FAQ

**Does it support password-protected PDFs?**
Yes. Set the `password` input field to unlock them. Wrong password returns a clear error.

**What is the maximum PDF file size?**
50 MB by default. You can raise this up to 500 MB via the `maxPdfMb` input parameter.

**Can it handle scanned PDFs (image-only)?**
Yes — enable `enableOcr: true`. Tesseract OCR is built in, supporting 100+ languages.

**Does it extract tables?**
Yes. Set `extractTables: true` to get tables as 2D arrays (rows × columns) per page. Tables are also rendered as Markdown tables in `markdown` output mode.

**Can I extract only specific pages?**
Yes — use `startPage` / `endPage` for a fixed range, or `maxPages` to cap the total. All are 1-indexed.

**What PDF sources are supported?**
Direct PDF URLs, Google Drive share links, Dropbox links, SharePoint / OneDrive links — all auto-resolved. Any publicly accessible PDF URL works.

**How do I skip pages with no text?**
Set `skipEmptyPages: true`. Useful for mixed PDFs where some pages are blank or image-only.

**Is it safe to use for confidential PDFs?**
Apify runs in isolated containers. PDFs are downloaded to memory, processed, and discarded after the run — they are not stored persistently.

**The PDF fails to download with HTTP 403.**
Since v1.11, a 403 from a bot-protected CDN is retried once with a browser TLS fingerprint. The run log says whether an Apify Proxy is in use without exposing proxy credentials. If the CDN also blocks the retry by IP, enable `proxyConfiguration` (residential group recommended), or use another public PDF host.

**A large PDF fails with no error message.**
Fixed in v1.10 — pdfplumber used to retain every parsed page's internal cache for the life of the run, so memory grew ~10MB/page and large or table-heavy PDFs (200+ pages) could exceed the Actor's memory limit and get killed before any error was logged. The Actor now re-opens its internal PDF handle periodically to keep memory flat regardless of page count.

***

### Data engine

- **pdf-inspector** (Rust) — blazing fast (~150ms), smart layout detection, Markdown output
- **pdfplumber** v0.11+ — Pure-Python fallback for complex layouts, table extraction
- **Tesseract OCR** — built-in fallback for scanned/image PDFs, 100+ languages supported

***

### Run it on a schedule / from your pipeline

**1. Apify Schedule.** Apify Console → **Schedules** → **+ New schedule**, pick PDF Text Extractor, set a cron such as `0 6 * * 1-5` (weekdays 06:00 UTC) and paste this input:

```json
{
  "urls": [
    "https://example.com/reports/invoice-001.pdf",
    "https://example.com/reports/invoice-002.pdf"
  ],
  "outputFormat": "markdown",
  "changeDetection": true,
  "changedOnly": true
}
```

**2. API call from your pipeline.** Run the actor and get the dataset items back in a single request:

```bash
curl -X POST "https://api.apify.com/v2/acts/gochujang~pdf-text-extractor/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d @input.json
```

`input.json` holds the same JSON as above. Synchronous runs wait up to 300 seconds; for bigger batches call `POST /v2/acts/gochujang~pdf-text-extractor/runs` and read the dataset when the run finishes.

**3. Webhooks.** For new-file triggers, have your storage or automation tool (S3 event, Zapier, Make, n8n) call the API above whenever a file arrives, and add an Apify webhook on `ACTOR.RUN.SUCCEEDED` (Actor → **Integrations**) to push the results to your system as soon as the run finishes.

### Works great with

Building a document pipeline? These actors work alongside this one:

- [Excel & CSV Extractor](https://apify.com/gochujang/excel-extractor) — Extract data from Excel files in the same pipeline
- [DOCX Text Extractor](https://apify.com/gochujang/docx-text-extractor) — For Word documents — same structured output format

***

### Related actors (same author)

- [SEC 13F Institutional Holdings Tracker](https://apify.com/gochujang/sec-13f-tracker) — Get 13F filing PDF URLs + QoQ holdings changes
- [Web Page → Markdown Converter](https://apify.com/gochujang/web-to-markdown) — HTML version of the same idea
- [Excel & CSV Extractor](https://apify.com/gochujang/excel-extractor) — Same idea for .xlsx/.csv files
- [DOCX Text Extractor](https://apify.com/gochujang/docx-text-extractor) — Same idea for Word documents
- [Image OCR Extractor](https://apify.com/gochujang/image-ocr-extractor) — Same idea for image files (PNG/JPG)

***

### Feedback

A short review helps researchers / analysts find it: [Leave a review on Apify Store](https://apify.com/gochujang/pdf-text-extractor#reviews)

**Keywords:** PDF extractor, PDF parser, PDF to text, PDF to markdown, RAG, table extraction, PDF scraper, document processing, OCR

# Actor input Schema

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

Public PDF URLs for PDF to text extraction or PDF to Markdown conversion. For a quick one-page test, use https://pdfobject.com/pdf/sample.pdf.

## `urlsText` (type: `string`):

Alternative to the URLs list above — paste multiple PDF URLs, one per line. Merged with 'urls' if both are provided.

## `base64Pdfs` (type: `array`):

List of base64-encoded PDF files to process directly (no download needed). Each item can be a plain base64 string, or an object with 'data' (base64 string) and optional 'filename'. Ideal for programmatic use: encode any PDF file with base64 and pass it here.

## `url` (type: `string`):

Single public PDF URL, used when urls is empty; for example https://pdfobject.com/pdf/sample.pdf.

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

Password for password-protected / encrypted PDFs. Applied to all URLs in this run. Leave empty for public PDFs.

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

PDF parser output: markdown gives an LLM-ready PDF to Markdown document (default). per_page returns one record per page; combined returns full text; both returns both; rag_chunks splits text into overlapping chunks with metadata.

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

Only used when outputFormat=rag_chunks. Target character count per chunk (≈ 500 tokens at 4 chars/token). Default: 2000.

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

Only used when outputFormat=rag_chunks. Characters of overlap between consecutive chunks for context continuity. Default: 200.

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

Detect and extract tables per page (as 2D arrays). Disabled by default — enabling this on complex PDFs significantly increases processing time and memory usage.

## `statementLedger` (type: `boolean`):

Parse bank/credit-card statements into a transaction ledger

## `extractImages` (type: `boolean`):

Add image_count field to each page record.

## `startPage` (type: `integer`):

First page to extract (1-indexed). Default = 1.

## `endPage` (type: `integer`):

Last page to extract (inclusive). Leave empty to use maxPages.

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

Stop after this many pages per PDF (from startPage). Ignored if endPage is set.

## `maxPdfMb` (type: `integer`):

Reject PDFs larger than this size in megabytes. Prevents out-of-memory failures on huge files.

## `firstNPages` (type: `integer`):

Extract only the first N pages of the document. Combine with lastNPages to extract a summary (front + back). Leave empty to use startPage/endPage/maxPages.

## `lastNPages` (type: `integer`):

Extract only the last N pages of the document. Combine with firstNPages to get a document summary. Leave empty to disable.

## `removeHeaderFooter` (type: `boolean`):

Detect and remove text lines that repeat across ≥30% of pages (typical headers/footers like company name, page count, document title). Applied to combined/markdown output.

## `removePageNumbers` (type: `boolean`):

Strip lines that contain only a page number (e.g. '42', 'Page 3 of 10', '- 5 -'). Applied to all output formats.

## `removeExcessBlankLines` (type: `boolean`):

Collapse 3 or more consecutive blank lines to a maximum of 2. Reduces whitespace noise in multi-column PDFs.

## `skipEmptyPages` (type: `boolean`):

Skip pages with no extractable text. Useful for mixed PDFs where some pages are blank or image-only (combine with enableOcr for best results).

## `changeDetection` (type: `boolean`):

Compute a content hash of each PDF's extracted text and compare it to the previous run (stored in the Actor's Key-Value Store). Every output record gets a 'changed' field (true/false) and 'content_hash'. Ideal for scheduling: run daily/weekly and only act when 'changed': true. On the first run everything is 'changed': true.

## `webhookUrl` (type: `string`):

When changeDetection=true, POST a JSON notification to this URL whenever a PDF's content changes. Payload includes url, content_hash, page_count, word_count, title, and detected_at. Works with Slack, Discord, Make, Zapier, n8n, or any HTTP endpoint. Leave empty to disable.

## `changedOnly` (type: `boolean`):

Requires changeDetection=true. When enabled, skip all output records (pages, chunks, markdown) for PDFs that have NOT changed since the last run. Only PDFs where changed=true produce output. Makes scheduled runs noise-free — zero records when nothing changed.

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

When enabled, pages with little or no extractable text (under 50 characters) are reprocessed with Tesseract OCR. Use for scanned PDFs; it increases processing time.

## `ocrLanguage` (type: `string`):

Tesseract language code. 'eng' for English (default), 'deu' for German, 'fra' for French. Multiple: 'eng+deu'. See Tesseract docs for all codes.

## `userAgent` (type: `string`):

Custom UA string for the HTTP request.

## `proxyConfiguration` (type: `object`):

Optional proxy for fetching PDFs. Some corporate CDNs (e.g. Akamai/Cloudflare bot management) block Apify's datacenter IPs with HTTP 403 even with a browser User-Agent. Enable Apify Proxy (residential recommended) if a PDF fails to download with a 403 error.

## `autoSummarize` (type: `boolean`):

When enabled, appends an 'auto_summary' field to the final summary record. Includes: top 10 most frequent meaningful words and the first sentence of each paragraph (naive section detection). Uses Python built-ins — no AI API needed.

## Actor input object example

```json
{
  "urls": [
    "https://pdfobject.com/pdf/sample.pdf"
  ],
  "urlsText": "",
  "base64Pdfs": [],
  "url": "",
  "outputFormat": "markdown",
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "extractTables": false,
  "statementLedger": false,
  "extractImages": false,
  "startPage": 1,
  "maxPages": 100,
  "maxPdfMb": 50,
  "removeHeaderFooter": false,
  "removePageNumbers": false,
  "removeExcessBlankLines": false,
  "skipEmptyPages": false,
  "changeDetection": false,
  "webhookUrl": "",
  "changedOnly": false,
  "enableOcr": false,
  "ocrLanguage": "eng",
  "userAgent": "",
  "proxyConfiguration": {},
  "autoSummarize": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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 = {
    "urls": [
        "https://pdfobject.com/pdf/sample.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gochujang/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://pdfobject.com/pdf/sample.pdf"] }

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

```

## MCP server setup

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