# PDF Text Extractor - Markdown & Page Citations (`gallant_fender/pageledger`) Actor

Extract public text-based PDFs into Markdown and JSON with page numbers and character offsets for RAG source citations. Includes best-effort tables. No OCR. $0.005 per successful document, plus platform usage.

- **URL**: https://apify.com/gallant\_fender/pageledger.md
- **Developed by:** [zihang](https://apify.com/gallant_fender) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 successfully extracted pdfs

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## PageLedger — PDF to Markdown and page-cited JSON

Turn a public, digital-text PDF into Markdown plus page-level JSON so your RAG pipeline, research workflow, or citation layer can point back to the page that supplied each passage. Each successful result keeps page numbers and zero-based character offsets in concatenated extracted document text.

Use PageLedger when the source document is already available at a public HTTPS URL and you need a small, structured extraction step before indexing or quoting it. It does not fetch from private networks, and it does not turn scans into text.

### What you receive

Each successfully extracted document is written to the default dataset with:

- `markdown`: page-headed Markdown for the document
- `pages`: per-page extracted text, `page`, and offsets in concatenated extracted document text (`char_start` and `char_end`)
- `source_url` and `source_id`: source provenance for the input URL
- `table_status` and `extraction_warnings`: review signals for table-heavy documents

Tables are best-effort. Complex visual layouts can need review before you use their cells as structured data.

### Run it

Send one to ten public HTTPS PDF URLs. This is the W3C PDF used in the verified example below:

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}
```

Each PDF must be 10 MiB or smaller and have at most 50 pages. Downloading and parsing share a 30-second budget, and cloud runs require at least 1 GiB of memory. PageLedger rejects private-network addresses and unsafe redirects, encrypted PDFs, and scan-only PDFs. If any page, including a blank page, has no extractable text, the document is reported as unsupported with `OCR_REQUIRED`; PageLedger does not perform OCR.

### Example result

The following is a shortened record from the W3C input above, captured from a successful cloud run. It shows the actual output fields and values used by this Actor:

```json
{
  "status": "ok",
  "chargeable": true,
  "pages": [
    {
      "page": 1,
      "text": "Dummy PDF file",
      "char_start": 0,
      "char_end": 14,
      "tables": [],
      "table_status": "best_effort",
      "status": "extracted"
    }
  ],
  "markdown": "## Page 1\n\nDummy PDF file\n",
  "table_status": "best_effort",
  "extraction_warnings": ["tables_are_best_effort"],
  "elapsed_ms": 543.4,
  "source_url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "source_id": "c712e96563e8f8b0868ca5150548c4fde964b2a7d94061dd66473c0bc3b0d29a",
  "url_log": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
}
```

### Call from Python or HTTP

The synchronous dataset endpoint returns the run's dataset items. Set your token in the environment; the following example is documentation only and is not executed by this repository.

```python
import json
import os
from urllib.request import Request, urlopen

token = os.environ["APIFY_TOKEN"]
request = Request(
    "https://api.apify.com/v2/actors/gallant_fender~pageledger/run-sync-get-dataset-items?format=json&maxTotalChargeUsd=0.02&timeout=90",
    data=json.dumps({
        "urls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]
    }).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urlopen(request, timeout=120) as response:
    items = json.load(response)
```

For raw HTTP, `POST https://api.apify.com/v2/actors/gallant_fender~pageledger/run-sync-get-dataset-items?format=json&maxTotalChargeUsd=0.02&timeout=90` with the same JSON body and a bearer token returns the same dataset items. The example limits the total charge and sets the Actor run timeout to 90 seconds. Do not automatically retry a timed-out synchronous request: a retry creates a new run and can create a new fee. URL de-duplication applies only within one run. See the [Apify synchronous dataset endpoint](https://docs.apify.com/api/v2/actor-run-sync-get-dataset-items-post) for timeouts and response options.

### Pricing

PageLedger charges **$0.005 for each successfully extracted PDF** through Apify Pay Per Event. Platform usage is charged separately to the caller. Failed, unsupported, or rejected PDFs do not trigger the `document-processed` event fee. A repeated URL that already completed in the same run is skipped and does not create another result or event fee.

# Actor input Schema

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

One to ten public HTTPS PDF URLs. Each PDF must be 10 MiB or less and no more than 50 pages. The prefilled W3C sample is a live run; if it succeeds, it costs $0.005 per document plus Apify platform usage.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}
```

# Actor output Schema

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

Page-cited Markdown and JSON extraction records for successful PDFs.

# 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.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("gallant_fender/pageledger").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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

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

```

## MCP server setup

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

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/NmXcat31CYTD0mc1p/builds/P7nPVP9kE45vDWBmt/openapi.json
