# PDF Citation Chunker (`skilled_glee/pdf-citation-chunker`) Actor

Deterministically convert public embedded-text PDFs into page-aware, citation-ready JSON chunks with provenance, SHA-256 stable IDs, and RAG-ready output for API, MCP, and AI-agent workflows.

- **URL**: https://apify.com/skilled\_glee/pdf-citation-chunker.md
- **Developed by:** [Dakota Myers](https://apify.com/skilled_glee) (community)
- **Categories:** Automation, Agents, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.22 / 1,000 pdf pages

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 Citation Chunker

Convert public PDF documents into clean, page-aware, citation-ready JSON chunks for AI agents, RAG pipelines, search systems, APIs, and automated document workflows.

PDF Citation Chunker downloads one or more public PDFs, extracts embedded text page by page, normalizes the content, divides it into configurable overlapping chunks, and returns structured records that preserve source provenance.

### Machine contract

**INPUT:** Public HTTP(S) embedded-text PDF URL or batch of URLs.

**OUTPUT:** Deterministic, page-aware, citation-ready JSON chunks with stable document/chunk IDs, SHA-256 hashes, and provenance.

**USE WHEN:** Preparing embedded-text PDFs for RAG, retrieval, vector ingestion, citation-grounded AI, APIs, MCP, or other machine processing.

**DO NOT USE WHEN:** The PDF is scanned or image-only and requires OCR.

**FAILURE MODEL:** One bad PDF does not destroy useful output from the rest of a batch.

**CURRENT BILLING:** Pay Per Event. The current published configuration is **$0.25 per 1,000 successfully processed text pages** plus an **Actor start event of $0.00005**. `page-processed` is charged only after useful text output; empty/no-text pages are not charged. Check the Actor Pricing section before running because published rates can change.

### What it does

For every extracted chunk, the Actor returns:

- Original PDF URL
- Resolved PDF URL after redirects
- Downloaded PDF byte size
- PDF-byte SHA-256 and stable document ID
- Document position within the batch
- Document title
- Source PDF page
- Document-wide chunk index
- Page-local chunk index
- Normalized text
- Character count
- Estimated token count
- SHA-256 content hash
- Stable chunk ID and chunking configuration
- Extraction method
- Processing timestamp

The result is structured for machine consumption rather than manual copy-and-paste.

Typical uses include:

- Retrieval-Augmented Generation (RAG)
- Vector database ingestion
- AI-agent document analysis
- Citation-aware retrieval
- Search indexing
- Document pipelines
- Knowledge-base ingestion
- Archival and research workflows
- MCP and API-based automation

### Input

#### Single PDF

```json
{
  "url": "https://example.com/document.pdf",
  "chunkSize": 3000,
  "overlap": 300
}
```

#### Multiple PDFs

Up to 25 PDFs can be submitted in a single run:

```json
{
  "urls": [
    "https://example.com/document-1.pdf",
    "https://example.com/document-2.pdf"
  ],
  "chunkSize": 3000,
  "overlap": 300
}
```

You may use either `url`, `urls`, or both.

Duplicate URLs are automatically removed while preserving their original order.

### Chunking

`chunkSize` controls the approximate maximum number of characters in each output chunk.

Default:

```text
3000 characters
```

Allowed range:

```text
500 - 20000
```

`overlap` controls how much text is carried from one chunk into the next.

Default:

```text
300 characters
```

Overlap helps preserve context across chunk boundaries when the output is used for embeddings, retrieval, or language-model processing.

Chunks remain page-aware. PDF page provenance is preserved for every result.

### Output

Each chunk is stored as a structured dataset record.

Example:

```json
{
  "sourceUrl": "https://example.com/document.pdf",
  "resolvedUrl": "https://cdn.example.com/document.pdf",
  "sourceByteSize": 482193,
  "documentSha256": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
  "documentId": "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
  "documentIndex": 1,
  "documentTitle": "Example Document",
  "pageStart": 4,
  "pageEnd": 4,
  "chunkIndex": 7,
  "pageChunkIndex": 2,
  "chunkId": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "chunking": {
    "chunkSize": 3000,
    "overlap": 300
  },
  "text": "Extracted document text...",
  "characterCount": 2874,
  "estimatedTokens": 719,
  "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "extractionMethod": "pypdf",
  "processedAt": "2026-08-25T00:00:00+00:00"
}
```

#### Run summary

Each run also generates a machine-readable `SUMMARY` record containing:

- Submitted document count
- Completed document count
- Successful document count
- Failed document count
- Total pages extracted
- Total chunks produced
- Chunk-size setting
- Overlap setting
- Overall run status
- Per-document outcomes (`inputUrl`, `status`, `pagesProcessed`, `chunksProduced`, and an unsuccessful reason when applicable)
- URLs not processed because a spending limit stopped the batch
- Processing timestamp

Example:

```json
{
  "submittedDocuments": 3,
  "completedDocuments": 3,
  "successfulDocuments": 2,
  "failedDocuments": 1,
  "totalPages": 49,
  "totalChunks": 121,
  "chunkSize": 1200,
  "overlap": 150,
  "status": "partial_success",
  "documentOutcomes": [
    {
      "inputUrl": "https://example.com/valid.pdf",
      "status": "success",
      "pagesProcessed": 49,
      "chunksProduced": 121
    },
    {
      "inputUrl": "https://example.com/scanned.pdf",
      "status": "failed",
      "reason": "no_extractable_text",
      "pagesProcessed": 0,
      "chunksProduced": 0
    }
  ],
  "unprocessedUrls": [],
  "processedAt": "2026-08-25T00:00:00+00:00"
}
```

### Partial failure handling

One bad PDF does not automatically destroy an entire batch.

If a document:

- Returns an HTTP error
- Is inaccessible
- Is malformed
- Is not actually a PDF
- Cannot be processed

the Actor records a machine-readable document outcome and continues with the remaining documents.

A batch is `success` only when every submitted document produces useful chunks. It is `partial_success` when useful output survives one or more failed documents, and `failed` when no document produces useful chunks.

If a user spending limit stops the batch, the summary status is `spending_limit_reached`. It includes completed document outcomes and `unprocessedUrls` for the URLs skipped because of that limit.

This makes the Actor suitable for autonomous pipelines where partial results are more useful than losing an entire batch because of one invalid source.

### PDF limitations

The current version extracts embedded PDF text.

Image-only or scanned PDFs without an embedded text layer produce a failed document outcome with reason `no_extractable_text`; they are not counted as successfully processed.

OCR is not currently performed.

Other current limitations:

- Maximum PDF size: 50 MB per document
- Maximum batch size: 25 PDFs
- Password-protected PDFs may fail
- Corrupted PDFs may fail
- Private URLs requiring unsupported authentication may fail

OCR and additional document formats may be added in future versions.

### Integrity metadata

Every chunk includes a SHA-256 hash calculated from the exact normalized chunk text.

`documentSha256` is the SHA-256 digest of the original downloaded PDF bytes. `documentId` is `sha256:<documentSha256>`, so it is stable for the same PDF regardless of input order or processing time.

`chunkId` is a SHA-256 digest of a canonical JSON object containing the version string `pdf-citation-chunker:chunk:v1`, `documentSha256`, page number, page-local chunk index, chunk text SHA-256, `chunkSize`, and `overlap`. It is stable for the same emitted chunk and changes when its document, page-local position, content, or chunking configuration changes.

`sourceUrl` preserves the submitted URL; `resolvedUrl` records the final HTTP response URL after redirects when available; `sourceByteSize` records downloaded PDF bytes; and `chunking` records the `chunkSize` and `overlap` used for that record. Together these fields support provenance, deduplication, and idempotent ingestion without relying on batch order or timestamps.

This allows downstream systems to:

- Detect duplicate chunks
- Verify content stability
- Build deterministic caches
- Track document changes
- Identify repeated data across runs

### Token estimates

Each result includes an approximate token count.

The estimate is intended for planning downstream LLM and embedding workloads. It is not tied to any specific model tokenizer and should not be treated as an exact billing value.

### API and automation

Because PDF Citation Chunker runs as an Apify Actor, it can be invoked through:

- Apify Console
- Apify API
- Other Actors
- Automated workflows
- Schedules
- Webhooks
- External applications
- Agentic workflows
- Machine-to-machine systems

The resulting citation chunks are stored in the default Apify dataset and can be retrieved programmatically.

### For AI agents and MCP

**Contract:** public embedded-text PDF URL → deterministic, page-aware citation-ready JSON chunks.

Use this Actor for PDF chunking, RAG ingestion, citation chunks, page provenance, and stable RAG chunk IDs. For scans or image-only PDFs, use **OCR Citation Chunker** instead.

### Designed for machine consumption

The Actor intentionally favors predictable JSON over human-oriented document formatting.

A typical pipeline may look like:

```text
PDF URL
   ↓
PDF Citation Chunker
   ↓
page-aware JSON chunks
   ↓
embedding model
   ↓
vector database
   ↓
RAG / AI agent / search system
```

The Actor can also be used as a preprocessing primitive inside larger automated systems.

### Privacy and responsible use

Only process documents you are authorized to access and process.

The Actor operates on URLs provided as run input and stores extracted results in the run's Apify storage.

Avoid submitting confidential or sensitive documents unless your Apify environment and intended workflow are appropriate for that data.

### Current capabilities

Version `0.1.x` includes:

- Single-PDF processing
- Multi-PDF batch processing
- Up to 25 PDFs per run
- Embedded-text extraction
- Page-aware chunking
- Configurable chunk size
- Configurable chunk overlap
- PDF provenance metadata
- SHA-256 chunk hashes
- Estimated token counts
- Partial-failure isolation
- Machine-readable run summaries
- Pay-per-event billing support
- Spending-limit awareness
- API-ready structured output

### Output philosophy

PDF Citation Chunker does one job:

> Turn a PDF into predictable, provenance-preserving chunks that another machine can immediately use.

No AI-generated summaries are inserted into the document.

No interpretation is performed.

No source text is intentionally rewritten.

The Actor focuses on creating a reliable document-processing primitive that can be composed with other systems.

# Actor input Schema

## `mode` (type: `string`):

chunk produces citation-ready records; preflight only validates PDFs and reports total page counts.

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

Optional when urls is supplied. Supply a public HTTP(S) embedded-text PDF URL. The Actor does not support cookies or authentication, and runtime validation enforces the HTTP(S) URL scheme. Maximum 50 MB. Scanned/image-only PDFs require OCR and are not supported by this Actor.

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

Optional when url is supplied. List of up to 25 unique public HTTP(S) embedded-text PDF URLs; no authentication or cookies. Each PDF must be 50 MB or smaller. Scanned/image-only PDFs require OCR and are not supported by this Actor.

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

Maximum approximate characters per deterministic output chunk. Use a size suited to downstream RAG, retrieval, or vector-ingestion limits.

## `overlap` (type: `integer`):

Characters carried from one chunk into the next for retrieval context. Must be smaller than chunkSize; runtime validation enforces this relationship.

## Actor input object example

```json
{
  "mode": "chunk",
  "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "chunkSize": 3000,
  "overlap": 300
}
```

# Actor output Schema

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

Dataset containing all successfully extracted citation-ready chunks.

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

JSON summary containing per-document outcomes, completed and unprocessed URLs, page and chunk counts, and a truthful run status: success, partial\_success, failed, or spending\_limit\_reached.

# 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 = {
    "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
};

// Run the Actor and wait for it to finish
const run = await client.actor("skilled_glee/pdf-citation-chunker").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 = { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" }

# Run the Actor and wait for it to finish
run = client.actor("skilled_glee/pdf-citation-chunker").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 '{
  "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
}' |
apify call skilled_glee/pdf-citation-chunker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,skilled_glee/pdf-citation-chunker"
        }
    }
}

```

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/H1c9OyB0AnRf8Sy4y/builds/R0sRH9o24uau7MPPg/openapi.json
