# PDF Revision Monitor - Text, Pages & Change Evidence (`maydit/pdf-revision-monitor`) Actor

Extract text from public PDFs with page citations and hashes. Compare complete observations for text or byte-only changes. Bounded downloads and parsing; no OCR or API key.

- **URL**: https://apify.com/maydit/pdf-revision-monitor.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 document observations

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?

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

![See the evidence in a PDF revision — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/pdf-revision-monitor-1d5b9c595a17.png?signature=k1Yfiked7CMOJguJCPZo)

### See the evidence in a PDF revision

For teams checking recurring reports and documents: extract the text layer of public PDFs with page references and hashes, then optionally compare complete observations. Bounded download and extraction controls keep the scope explicit.

[Open this Actor in Console](https://console.apify.com/actors/moyYsmtesSbenplBj/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

### Try this Actor

- **First run:** Run the existing **Quick start** example, `{}`, to inspect the single Census retail PDF supplied by the default. Review its extraction completeness before enabling comparison.
- **Read the results:** Open the default Dataset for document observations and **SUMMARY** for processing status. Check **Complete extraction**, the extracted-page count and the baseline status before relying on a comparison.
- **Keep in mind:** There is no OCR, visual layout analysis or table-cell extraction. A byte-only change may be formatting or metadata; partial extraction does not replace a complete comparison baseline.

**Extract Current Census Retail Report** — copy this input into the JSON editor in Console.

```json
{}
```

### Result preview

The dataset view highlights these columns:

- **PDF URL** · `url`
- **Document ID** · `documentId`
- **Total pages** · `pageCount`
- **Pages extracted** · `extractedPageCount`
- **Characters extracted** · `characterCount`
- **Complete extraction** · `completeExtraction`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

### Usage and pricing

Base result rate on the FREE tier: **$10 per 1,000 document observations**. The [Pricing tab](https://apify.com/maydit/pdf-revision-monitor/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

***

### Full guide

Extract text from public PDF URLs with page citation links and stable content hashes. Optionally compare each document with its previous complete observation to identify changed pages, removed pages or byte-only changes. No source API key or OCR service is required.

#### Quick start and exact examples

Default input extracts the current public Census retail-report PDF:

```json
{}
```

Keep your own report identity and external reference:

```json
{"documents":[{"url":"https://www.census.gov/retail/marts/www/marts_current.pdf","documentId":"retail-report","externalId":"research-retail-001"}],"maxPagesPerDocument":100}
```

Compare future observations at a stable report URL:

```json
{"documents":[{"url":"https://www.census.gov/retail/marts/www/marts_current.pdf","documentId":"census-monthly-retail-release"}],"comparePrevious":true,"baselineKey":"example-census-report"}
```

The first complete comparison run creates a baseline. Its row says `comparisonStatus: no_previous_baseline`; `SUMMARY.documents[].baselineUpdated` confirms the actual history commit. A partial first observation cannot create history and says `changeType: partial_observation`. Unchanged successful checks still return and bill an observation. Source publication and revision statements remain in the extracted text; a URL named “current” does not establish that every figure is the latest revised estimate.

#### Input and limits

`documents` accepts up to 20 public URL strings or objects with `url`, optional `documentId`, and optional `externalId`. The default identity is the supplied URL. Supply the same intentional `documentId` to compare versions whose download URL changes. IDs must be unique within a run.

- `maxPagesPerDocument`: 100 by default and at most 100; pages are read starting at page 1. A shorter cap produces a partial observation.
- `maxFileSizeMb`: 10 MiB by default, at most 20. Larger responses are rejected before parsing.
- `maxCharactersPerDocument`: 250,000 by default, at most 1,000,000. Reaching the character limit makes extraction partial.
- `maxRunSeconds`: 240 by default, also bounded by the platform deadline minus 25 seconds. A document parser is limited to at most 60 seconds and the actual remaining budget.

Only public HTTP(S) ports 80/443 are allowed. DNS results and redirect destinations are checked; robots restrictions are honored. Downloads need no custom headers, cookies, passwords or credentials. Parsing runs in a bounded worker without evaluating embedded PDF scripts.

#### Output

One dataset row represents one PDF with usable extracted text:

- `url`, `inputUrl`, `documentId`, `externalId`, `observedAt`: source identity and observation time.
- `byteLength`, `byteHash`, `contentHash`: exact downloaded bytes and normalized page-text identity.
- `pageCount`, `extractedPageCount`, `characterCount`, `completeExtraction`, `limitsReached`, `noTextPages`: actual coverage.
- `pages`: page number, extracted text, text hash and `citationUrl` ending in `#page=N`.
- `metadata`: selected source PDF metadata when present. These values are not verified publication dates.
- `comparisonStatus`, `previousObservedAt`, `changeType`, `changedPages`, `removedPages`, `unchangedPageCount`: comparison evidence. Before/after excerpts are bounded previews of the page, not an exact inline word diff.

`changeType` distinguishes `text_changed`, `bytes_only_changed`, `unchanged`, `baseline`, `partial_observation` and `not_compared`. Page comparisons align by page number: inserting a page can shift later pages and create several changed-page entries. Byte-only changes can reflect metadata or formatting; no visual-change analysis is claimed.

#### History and failure behavior

`comparePrevious` is opt-in. History is stored in `maydit-pdf-revision-baselines-v1`, keyed by document ID, namespace and extractor version. A complete baseline stores hashes, page numbers and bounded text previews. It never stores a second copy of the PDF bytes.

Only a completely parsed document whose output was saved advances history. Partial/capped/no-text pages and failed output preserve the earlier complete baseline; removals are never inferred from a partial extraction. A saved final row that reaches the spending cap still counts and can advance complete history. Use one writer per document ID and namespace: the pre-write conflict check is not an atomic lock.

Partial documents with some usable text return clearly marked observations. All-textless, encrypted, malformed, blocked, or oversized documents are failures reported in `SUMMARY`, with no fake dataset row. A run with no usable text fails; a normal spending-limit stop succeeds with `status: budget_limited`, including when no row was affordable.

#### Supported content and pricing

This reads a PDF's text layer. It does not perform OCR, handwriting recognition, image extraction, semantic legal review, or structured table-cell extraction. Reading order in multicolumn layouts can be imperfect. Empty page text does not prove that a page is visually blank. Text hashes describe the extracted representation, not the visual document.

The billing unit is one saved document observation, including unchanged and explicitly partial observations. Individual pages do not add billable rows. Consult the Store Pricing tab for current prices; failed documents create no result charge. Platform/start charges, if listed, are separate.

Sources are user-supplied public PDFs. Use documents you are authorized to process. Parsing uses [Mozilla PDF.js](https://mozilla.github.io/pdf.js/); the default is a real [U.S. Census report](https://www.census.gov/retail/marts/www/marts_current.pdf).

### Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

### Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.

# Actor input Schema

## `documents` (type: `array`):

Up to 20 URLs or objects {url,documentId,externalId}. documentId intentionally links changing source URLs to one report; default identity is the supplied URL. No passwords or private downloads.

## `maxPagesPerDocument` (type: `integer`):

Read from page 1 through this cap. Larger documents return a partial observation; no baseline update or removed-page assertion.

## `maxFileSizeMb` (type: `integer`):

Reject a larger response before parsing. Applies to every document; redirects and DNS destinations are checked.

## `maxCharactersPerDocument` (type: `integer`):

Stop extraction at this bound and mark the observation partial. Partial text never replaces a complete baseline.

## `comparePrevious` (type: `boolean`):

Opt in to named-store history. First complete run establishes a baseline. Unchanged checks still return and bill a real observation.

## `baselineKey` (type: `string`):

One writer per namespace. A separate namespace starts independent history. Letters, digits, underscores and hyphens only.

## `maxRunSeconds` (type: `integer`):

Total work budget, also bounded by the platform deadline minus 25 seconds. Unfinished work is reported.

## Actor input object example

```json
{
  "documents": [
    {
      "url": "https://www.census.gov/retail/marts/www/marts_current.pdf",
      "documentId": "census-monthly-retail-release"
    }
  ],
  "maxPagesPerDocument": 100,
  "maxFileSizeMb": 10,
  "maxCharactersPerDocument": 250000,
  "comparePrevious": false,
  "baselineKey": "default",
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

Successful extracted source observations. Inspect per-row completeness and SUMMARY before using comparisons.

## `runReport` (type: `string`):

Readable summary of saved records, reported source coverage, limits and next steps. Available when this run saves its SUMMARY.

## `csvExport` (type: `string`):

Download saved dataset records as a CSV file for spreadsheets and other tools.

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

Saved counts, failed sources, completeness and spending/time limits.

# 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 = {
    "documents": [
        {
            "url": "https://www.census.gov/retail/marts/www/marts_current.pdf",
            "documentId": "census-monthly-retail-release"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/pdf-revision-monitor").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 = { "documents": [{
            "url": "https://www.census.gov/retail/marts/www/marts_current.pdf",
            "documentId": "census-monthly-retail-release",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/pdf-revision-monitor").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 '{
  "documents": [
    {
      "url": "https://www.census.gov/retail/marts/www/marts_current.pdf",
      "documentId": "census-monthly-retail-release"
    }
  ]
}' |
apify call maydit/pdf-revision-monitor --silent --output-dataset

```

## MCP server setup

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

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/moyYsmtesSbenplBj/builds/vKYH6dsMGJwdkGLUc/openapi.json
