# PDF to RAG Markdown Chunks for Embeddings (`awesome_highboy/docforge`) Actor

Convert PDFs into token-bounded Markdown chunks for RAG, embeddings, and vector databases (Pinecone, Chroma, Weaviate, Qdrant). Set maxTokens + overlap; get clean chunks with page number, token count, and SHA-256 content hash for dedup. JSON dataset ready for any LLM pipeline.

- **URL**: https://apify.com/awesome\_highboy/docforge.md
- **Developed by:** [Adam](https://apify.com/awesome_highboy) (community)
- **Categories:** AI, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page parseds

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/platform/actors/running/actors-in-store#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

## DocForge: PDF → AI-Ready Markdown Chunks for RAG

Turn PDF files you own into clean, deterministic, token-bounded text chunks — each tagged with the **page it came from** — ready for RAG pipelines and embeddings.

### What it does

DocForge takes a list of PDF URLs that **you own or are authorized to process**, downloads each file, and extracts its text **page by page**. Each page's text is cleaned of the artifacts that normally wreck embeddings — words split by a hyphen at a line break, ligatures, control characters, and the runs of stray whitespace PDFs emit between columns — and then split into **sentence-aware**, token-bounded chunks. Each chunk is emitted as a structured dataset record carrying its source document, **its true originating page number**, a chunk index, an estimated token count, and a content hash. A final run summary reports how many pages were parsed and how many chunks were emitted.

The chunker is sentence-aware and overlap-aware: it packs whole sentences up to your target chunk size instead of cutting mid-sentence, carries a configurable overlap between consecutive chunks, and guarantees no chunk exceeds your configured `maxTokens`. Each chunk's text is emitted in the `markdown` field as clean extracted text, so it drops straight into a vector store or embedding job — and because every chunk knows its page, retrieved passages can cite the exact page they came from.

Before any work begins, DocForge requires an explicit ownership attestation. If that attestation is not set, the run is rejected with zero billing. Pages that contain no extractable text (e.g. scanned images) are skipped rather than emitted as blank chunks, and documents that fail to download or parse are caught, logged, and skipped rather than guessed at, so the dataset only contains content that was actually extracted.

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `pdfUrls` | array of strings | Yes | URLs of PDFs you own or are authorized to process. |
| `chunking` | object | No | Chunking options. Prefilled with `maxTokens: 512` and `overlapTokens: 64`. |
| `ownership_attestation` | boolean | Yes | You confirm you own or are authorized to process these documents. Must be `true` or the run is rejected before any billing. |

The `chunking` object accepts:

- `maxTokens` (default `512`) — the maximum estimated token size of each chunk; no chunk exceeds this.
- `overlapTokens` (default `64`) — how much each chunk overlaps the previous one, to preserve context across chunk boundaries.

Token counts are word-based estimates (approximately words × 1.3), not exact tokenizer counts.

### Output

DocForge writes two record types to the dataset, distinguished by `record_type`.

**`chunk`** — one record per emitted text chunk:

| Field | Type | Description |
|---|---|---|
| `record_type` | string | Always `chunk`. |
| `source_doc` | string | The source PDF URL the chunk came from. |
| `page_number` | integer | The 1-based page the chunk's text was extracted from. Use it to cite or filter retrieved passages by page. |
| `chunk_index` | integer | Zero-based index of the chunk within its document (continuous across pages). |
| `markdown` | string | The chunk's cleaned text (de-hyphenated, whitespace- and unicode-normalized). |
| `token_count` | integer | Estimated token count for the chunk (never exceeds `maxTokens`). |
| `content_hash` | string | Deterministic `sha256:<64 hex>` hash of the chunk text. |

**`run_summary`** — one record per run:

| Field | Type | Description |
|---|---|---|
| `record_type` | string | Always `run_summary`. |
| `pages_parsed` | integer | Total document pages parsed in the run. |
| `chunks_emitted` | integer | Total chunks emitted in the run. |

### Pricing

DocForge uses Apify Pay-Per-Event pricing. You are billed only for what a successful, gated run actually does:

| Event | Price (USD) | When it fires |
|---|---|---|
| `actor_run_start` | $0.02 | Once per run, after the run's gates pass. |
| `page_parsed` | $0.003 | Per document page converted to text. |
| `chunk_emitted` | $0.0005 | Per RAG chunk emitted. |

**Example run cost.** Processing a single 40-page PDF that yields 120 chunks:

- 1 × `actor_run_start` = $0.02
- 40 × `page_parsed` = $0.12
- 120 × `chunk_emitted` = $0.06
- **Total ≈ $0.20**

If the ownership attestation is missing, the run is rejected with **zero** billing.

### Why this Actor

- **Page-accurate citations.** Every chunk carries the real page it was extracted from, so retrieved passages can point back to the exact page — not a placeholder. (On a 14-page sample paper, that's 14 distinct page numbers across the chunks instead of one.)
- **Clean text, not PDF soup.** De-hyphenation reconnects words broken across line wraps (≈150 fixes on a typical research PDF), ligatures and full-width characters are unicode-normalized, control characters are stripped, and runs of whitespace are collapsed — so your embeddings see real words, not `inter- national`.
- **Sentence-aware chunking.** Chunks are packed from whole sentences up to your `maxTokens` instead of being cut mid-sentence, with configurable overlap to preserve context across boundaries. No chunk ever exceeds `maxTokens`.
- **Deterministic, idempotent output.** Every chunk carries a `sha256:` content hash computed directly from its text, so identical input produces identical hashes — ideal for deduplication, change detection, and re-run safety.
- **Ownership-gated by design.** A required attestation must be `true` before any processing or billing happens. DocForge runs on PDFs you provide and are authorized to use; it does not crawl or scrape third-party sites.
- **No invented content.** Text is extracted deterministically with no LLM in the loop. Empty/scanned pages and documents that fail to fetch or parse are caught, logged, and skipped — they are not hallucinated or padded. The run summary reflects only what was genuinely parsed and emitted.

### About this Actor

This Actor is AI-authored and operated under the publisher's LLC. It uses `Actor.charge()` strictly to bill the customer for the Pay-Per-Event units above; the Actor contains no payout or money-out capability. All claims here reflect behavior present in the Actor's code.

# Actor input Schema

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

URLs of PDFs you OWN or are authorized to process.

## `chunking` (type: `object`):

Chunking options. maxTokens (default 512) — max estimated tokens per chunk; no chunk exceeds it. overlapTokens (default 64) — context overlap carried between consecutive chunks. Chunking is sentence-aware: whole sentences are packed up to maxTokens rather than cut mid-sentence.

## `ownership_attestation` (type: `boolean`):

I own/am authorized to process these documents (REQUIRED)

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "chunking": {
    "maxTokens": 512,
    "overlapTokens": 64
  },
  "ownership_attestation": true
}
```

# 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 = {
    "pdfUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ],
    "chunking": {
        "maxTokens": 512,
        "overlapTokens": 64
    },
    "ownership_attestation": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("awesome_highboy/docforge").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 = {
    "pdfUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],
    "chunking": {
        "maxTokens": 512,
        "overlapTokens": 64,
    },
    "ownership_attestation": True,
}

# Run the Actor and wait for it to finish
run = client.actor("awesome_highboy/docforge").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "chunking": {
    "maxTokens": 512,
    "overlapTokens": 64
  },
  "ownership_attestation": true
}' |
apify call awesome_highboy/docforge --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=awesome_highboy/docforge",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/r5mPUzJG8Bnt814kc/builds/is2BiHBMvewe4zsCZ/openapi.json
