# PDF Change Monitor & Diff API (`abiding_knapweed/pdf-change-monitor-diff-api`) Actor

Compare PDF revisions, extract Markdown/JSON with OCR and tables, or monitor a PDF URL for changes. Returns page-level evidence, hashes, and structured output.

- **URL**: https://apify.com/abiding\_knapweed/pdf-change-monitor-diff-api.md
- **Developed by:** [kouki ohba](https://apify.com/abiding_knapweed) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 text pages

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/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

## PDF Change Monitor & Diff API — OCR, Tables, Evidence

Compare two PDF revisions, run a durable change monitor for one PDF URL, or
extract PDF content to Markdown and JSON. Results include page references,
bounded coordinates, table-cell changes when the table is eligible, OCR
provenance, and content-addressed evidence.

This Actor reports factual changes. It does not decide legal meaning,
materiality, or whether a document is safe to rely on without human review.

This Actor is published on Apify Store:
[PDF Change Monitor & Diff API](https://apify.com/abiding_knapweed/pdf-change-monitor-diff-api).

### Why use this Actor

- Run a PDF diff with page-level text, table, and structural evidence.
- Keep a named baseline and use the change monitor for later checks of the
  same public PDF URL.
- Extract digital or scanned PDFs with deterministic local OCR. No separate
  document-processing API key is required.
- Bound work with file, page, OCR-time, and customer event-charge limits.
- Receive typed Dataset records plus Markdown, JSON, billing, and evidence
  artifacts for review.

### Modes and limits

- `extract` accepts 1–20 PDF sources.
- `compare` accepts one old and one new revision.
- `monitor` accepts one stable HTTP(S) URL and a stable `monitorId`. It uses a
  dedicated named Key-value store so later scheduled runs see the same baseline.
- Each PDF is limited to 50 MiB and 500 pages. One run downloads at most
  200 MiB in total.
- OCR is limited to 60 seconds per OCR page. Available languages are `eng`,
  `jpn`, `deu`, `fra`, and `spa`.
- `ocrMode=auto` also selects a page when one raster image covers at least 30%
  of the page and the native layer has fewer than 128 non-whitespace
  characters. Use `off` for image-heavy documents that should remain
  native-only. The run must reserve the full OCR event budget before OCR starts.
- Cell-level differences are emitted only for structurally unambiguous native
  text tables. OCR tables, merged cells, page-spanning tables, and ambiguous
  matches use a table or page fallback with a reason.
- Compare and monitor do not publish a partial success when any required page
  is uncertain.

The Dataset contains typed records and one final `run_summary`. The default
Key-value store can contain `OUTPUT.json`, `PERSISTENCE.json`, `BILLING.json`,
`document.md`, `diff.json`, `diff.md`, and bounded evidence images. A link can
return 404 when its mode did not produce that artifact.

### Extract

```bash
curl -X POST "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs" \
  -H "authorization: Bearer <APIFY_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"mode": "extract", "sources": [{"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"}], "ocrMode": "auto", "ocrLanguages": ["eng"]}'
```

```javascript
const input = {
  "mode": "extract",
  sources: [{ url: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" }],
  ocrMode: "always",
  ocrLanguages: ["eng", "jpn"],
};
const response = await fetch(
  "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs",
  { method: "POST", headers: { "authorization": "Bearer <APIFY_TOKEN>", "content-type": "application/json" }, body: JSON.stringify(input) },
);
```

```python
import requests

input_data = {
    "mode": "extract",
    "sources": [{"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"}],
    "ocrMode": "always",
    "ocrLanguages": ["eng", "jpn"],
}
requests.post(
    "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs",
    headers={"Authorization": "Bearer <APIFY_TOKEN>"},
    json=input_data,
    timeout=30,
).raise_for_status()
```

### Compare

```bash
curl -X POST "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs" \
  -H "authorization: Bearer <APIFY_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"mode": "compare", "oldSource": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-old.pdf"}, "newSource": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf"}, "ocrLanguages": ["eng"]}'
```

```javascript
const input = {
  "mode": "compare",
  oldSource: { url: "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-old.pdf" },
  newSource: { url: "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf" },
  ocrLanguages: ["eng"],
};
await fetch("https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs", {
  method: "POST", headers: { "authorization": "Bearer <APIFY_TOKEN>", "content-type": "application/json" }, body: JSON.stringify(input),
});
```

```python
import requests

requests.post(
    "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs",
    headers={"Authorization": "Bearer <APIFY_TOKEN>"},
    json={
        "mode": "compare",
        "oldSource": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-old.pdf"},
        "newSource": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf"},
        "ocrLanguages": ["eng"],
    },
    timeout=30,
).raise_for_status()
```

### Monitor

Use an Apify Task and Schedule for recurring checks. Keep the `monitorId`,
source URL, and named Key-value store unchanged between runs.

```bash
curl -X POST "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs" \
  -H "authorization: Bearer <APIFY_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"mode": "monitor", "source": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf"}, "monitorId": "release-fixture-txt-03", "stateStore": "pdf-change-monitor-state", "ocrLanguages": ["eng"]}'
```

```javascript
const input = {
  "mode": "monitor",
  source: { url: "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf" },
  monitorId: "release-fixture-txt-03",
  stateStore: "pdf-change-monitor-state",
  ocrLanguages: ["eng"],
};
await fetch("https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs", {
  method: "POST", headers: { "authorization": "Bearer <APIFY_TOKEN>", "content-type": "application/json" }, body: JSON.stringify(input),
});
```

```python
import requests

requests.post(
    "https://api.apify.com/v2/acts/abiding_knapweed~pdf-change-monitor-diff-api/runs",
    headers={"Authorization": "Bearer <APIFY_TOKEN>"},
    json={
        "mode": "monitor",
        "source": {"url": "https://raw.githubusercontent.com/ugin-man/pdf-change-monitor-diff-api/494b4a58dcdd18e80016623dabeb956028bb0f01/quality/fixtures/pdfs/txt-03-new.pdf"},
        "monitorId": "release-fixture-txt-03",
        "stateStore": "pdf-change-monitor-state",
        "ocrLanguages": ["eng"],
    },
    timeout=30,
).raise_for_status()
```

### Charging, errors, and data

Only completed product events are charged. Blocked URLs, validation failures,
fetch failures, corrupt or unsupported PDFs, budget rejection, sandbox failure,
and persistence failure have no charge. Monitor mode has one flat
`monitor-check` event and does not also emit `text-page` events.

Current custom event prices are $0.002 per `text-page`, an additional $0.010
per `ocr-page`, $0.030 per `revision-comparison`, and $0.020 per
`monitor-check`. The Actor uses Pay per event + usage, so Apify platform
compute, storage, and network usage are billed separately. See
[pricing](docs/pricing.md).

Run storage follows the retention selected in your Apify account. Monitor
baselines remain in the named Key-value store until you delete that store in
Apify Console or with the API. Raw PDF bytes are not retained in the monitor
state by default. See [privacy and retention](docs/privacy-and-retention.md).

Security design and responsible reporting are in [security](docs/security.md).
Report a problem through the public
[Actor Issues page](https://apify.com/abiding_knapweed/pdf-change-monitor-diff-api/issues).

# Actor input Schema

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

Choose extraction, comparison, or one scheduled monitor check.

## `ocrMode` (type: `string`):

Control whether OCR is disabled, selected automatically, or always used. Auto also selects sparse native pages with a raster image covering at least 30% of the page.

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

Select up to five OCR languages; the Actor applies a stable language order.

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

Include native table and cell records when the PDF exposes them.

## `includePageImages` (type: `boolean`):

Persist bounded page evidence images when they are available.

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

Reject a PDF whose page count exceeds this limit.

## `sources` (type: `array`):

Provide one to twenty PDF URLs for extract mode.

## `oldSource` (type: `object`):

Provide the previous PDF revision for compare mode.

## `newSource` (type: `object`):

Provide the current PDF revision for compare mode.

## `source` (type: `object`):

Provide the stable HTTP or HTTPS PDF URL for monitor mode.

## `monitorId` (type: `string`):

Stable identifier used to isolate one monitor baseline.

## `stateStore` (type: `string`):

Optional named Key-value store that retains monitor state.

## `qualityAttemptId` (type: `string`):

Optional hidden marker used only to bind an external quality run.

## Actor input object example

```json
{
  "mode": "extract",
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "extractTables": true,
  "includePageImages": false,
  "maxPages": 500
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

No description

## `result` (type: `string`):

OUTPUT.json contains the final result and its errors array.

## `errors` (type: `string`):

The errors array is in OUTPUT.json; matching recordType=error rows are in Dataset items.

## `documentMarkdown` (type: `string`):

Present for successful extract mode deliveries.

## `diffJson` (type: `string`):

Present for successful compare mode deliveries.

## `diffMarkdown` (type: `string`):

Present for successful compare mode deliveries.

## `billing` (type: `string`):

No description

## `evidence` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("abiding_knapweed/pdf-change-monitor-diff-api").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("abiding_knapweed/pdf-change-monitor-diff-api").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 '{}' |
apify call abiding_knapweed/pdf-change-monitor-diff-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abiding_knapweed/pdf-change-monitor-diff-api",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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