# Website Change History - Wayback Machine Snapshots + Diff (`dennydevelops/wayback-change-history`) Actor

List Wayback Machine snapshots of any URL and see exactly what changed between two dates: pricing pages, terms of service, policies, competitor sites.

- **URL**: https://apify.com/dennydevelops/wayback-change-history.md
- **Developed by:** [Musedenny](https://apify.com/dennydevelops) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 snapshots

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?

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

## Website Change History – Wayback Machine Snapshots + Diff

See **how any web page changed over time** using the Internet Archive's Wayback Machine. List every archived capture of a URL, or get a **readable "what changed" report** between two dates: the main text of both versions is extracted, compared and summarised, with price changes called out.

Good for pricing pages, terms of service, privacy policies, job pages, product pages and competitor sites.

Uses the official Wayback CDX API and archived pages only. No login, no API key.

### Modes

| Mode | What you get |
|---|---|
| `snapshots` | One row per archived capture: time, HTTP status, size, content digest, link. Dedupe with `collapse` (only when content changed / one per day / month / year). `onlyNewSinceLastRun` lists only captures newer than the previous run. |
| `compare` | The captures closest to `compareFrom` and `compareTo` (or the newest), compared: summary, price/figure changes, added/removed lines, unified diff. |
| `timeline` | Consecutive comparisons across a date range (e.g. one capture per month, up to `maxComparisonsPerUrl`), so you see *when* a page changed. |
| `monitor` | Schedule it: each run compares the capture seen last time with the newest one and reports only new changes. The first run saves a baseline (no comparison charge). |

### Input example

```json
{
  "urls": ["https://www.notion.com/pricing"],
  "mode": "compare",
  "compareFrom": "2024-06-01",
  "compareTo": "2025-09-01"
}
```

Dates accept `YYYY-MM-DD`, `YYYY-MM` or `YYYY`.

### Output examples

Snapshot:

```json
{"type": "snapshot", "url": "https://stripe.com/pricing", "timestamp": "20250101130720",
 "capturedAt": "2025-01-01T13:07:20Z", "originalUrl": "https://stripe.com/pricing", "statusCode": 200,
 "mimeType": "text/html", "digest": "OSQTIT5IQIOPY5UAH37AE2MIHOYWZZ6N", "lengthBytes": 127566,
 "archiveUrl": "https://web.archive.org/web/20250101130720/https://stripe.com/pricing"}
```

Comparison (real result, shortened):

```json
{
  "type": "comparison",
  "comparisonKind": "dates",
  "url": "https://www.notion.com/pricing",
  "from": {"capturedAt": "2024-11-16T22:48:46Z", "archiveUrl": "https://web.archive.org/web/20241116224846/..."},
  "to": {"capturedAt": "2025-08-31T18:38:59Z", "archiveUrl": "https://web.archive.org/web/20250831183859/..."},
  "summary": "Note: nearest captures are 168 / 0 days from the requested dates. 39 change(s): 168 line(s) added, 156 removed (55% of the text differs). Prices/figures removed: $0, $10, $15, $8 per member, $10 per member, $15 per seat; added: $0 per seat, $20 per seat. …",
  "changed": true,
  "similarity": 0.4471,
  "linesAdded": 168,
  "linesRemoved": 156,
  "pricesRemoved": ["$8 per member", "$10 per member", "$15 per seat"],
  "pricesAdded": ["$0 per seat", "$20 per seat"],
  "changes": [{"type": "changed", "before": ["…"], "after": ["…"]}],
  "unifiedDiff": "--- before\n+++ after\n@@ …",
  "requestedFrom": "20240601", "fromGapDays": 168, "toGapDays": 0
}
```

Problems with a single URL (no captures, archive errors) come back as `{"type": "error", "url": …, "error": …}` rows and do not stop the run (only the $0.0003 URL lookup is charged). If the archive fails for 3 URLs in a row, the run stops so you are not charged for an outage.

### Pricing (pay per event + usage)

| Event | Price | When |
|---|---|---|
| `apify-actor-start` | $0.001 | Apify built-in event, charged once per run (per GB of memory above 1 GB). Covers start-up cost and deters empty runs. |
| `url-lookup` | $0.0003 | One URL looked up in the Wayback Machine index (charged even if it has no captures). |
| `snapshot` | $0.0008 | One archived capture listed. |
| `comparison` | $0.015 | Two archived captures fetched, main text extracted and compared. |

Examples: a year of daily captures of one page = $0.001 + $0.0003 + 365 × $0.0008 = $0.29. A 12-month timeline of one page = 12 comparisons ≈ $0.18. A monitor run with no new capture costs about $0.0013.

Plus Apify platform usage for the run itself (server time), typically **$0.0002–0.0005 per run** because the Actor is lightweight and capped at 512 MB. The run stops cleanly when your maximum cost per run is reached.

### How the text comparison works

Archived pages are fetched exactly as captured (`id_` mode, no Wayback toolbar). The main text is taken from `<main>`/`<article>` when present, otherwise the body without navigation, header, footer, scripts and forms. Texts are compared block by block; amounts like `$29/mo`, `€12.50 per month`, `20%` are listed separately so price changes stand out.

### Limitations

- The archive only has what it captured. Busy pages are captured daily; small sites may have months between captures. The result always shows the actual capture dates, and the summary notes it when they are more than 30 days from the dates you asked for.
- Pages that build their content with JavaScript may have little text in the archived HTML.
- archive.org can be slow or rate-limit; the Actor paces requests (one every 0.5 s) and retries with backoff.

### Local development

```bash
pip install -r requirements.txt pytest pytest-asyncio respx
pytest          # HTTP mocked
python -m src   # reads storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

Pages to look up, e.g. https://stripe.com/pricing (up to 500).

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

snapshots: list captures. compare: what changed between compareFrom and compareTo. timeline: compare consecutive captures across the date range. monitor: compare the capture seen last run with the newest (schedule it).

## `fromDate` (type: `string`):

YYYY-MM-DD, YYYY-MM or YYYY. Used by snapshots and timeline.

## `toDate` (type: `string`):

YYYY-MM-DD, YYYY-MM or YYYY. Used by snapshots and timeline.

## `collapse` (type: `string`):

Remove near-duplicate captures: keep only changed content, or one per hour/day/month/year.

## `onlySuccessful` (type: `boolean`):

Only use captures where the page loaded successfully (HTTP 200).

## `includeSubpages` (type: `boolean`):

Snapshots mode: match every URL starting with this one (prefix match).

## `maxSnapshotsPerUrl` (type: `integer`):

Max snapshots per URL.

## `onlyNewSinceLastRun` (type: `boolean`):

Snapshots mode: remember the newest capture per URL and list only newer ones next time.

## `compareFrom` (type: `string`):

Compare mode: the capture closest to this date is the 'before' version.

## `compareTo` (type: `string`):

Compare mode: the 'after' version. Empty = newest capture.

## `timelineGranularity` (type: `string`):

Timeline mode: compare one capture per day, month or year.

## `maxComparisonsPerUrl` (type: `integer`):

Timeline mode: maximum number of comparisons per URL.

## `includeFullDiff` (type: `boolean`):

Include the full line-by-line diff in each comparison.

## `stateStoreName` (type: `string`):

Named key-value store used by monitor mode and only-new snapshots.

## Actor input object example

```json
{
  "urls": [
    "https://stripe.com/pricing"
  ],
  "mode": "snapshots",
  "fromDate": "2025-01-01",
  "collapse": "digest",
  "onlySuccessful": true,
  "includeSubpages": false,
  "maxSnapshotsPerUrl": 100,
  "onlyNewSinceLastRun": false,
  "timelineGranularity": "month",
  "maxComparisonsPerUrl": 12,
  "includeFullDiff": true,
  "stateStoreName": "wayback-change-history-state"
}
```

# Actor output Schema

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

Archived captures and change reports.

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

Totals for this run (JSON).

# 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://stripe.com/pricing"
    ],
    "fromDate": "2025-01-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dennydevelops/wayback-change-history").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://stripe.com/pricing"],
    "fromDate": "2025-01-01",
}

# Run the Actor and wait for it to finish
run = client.actor("dennydevelops/wayback-change-history").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://stripe.com/pricing"
  ],
  "fromDate": "2025-01-01"
}' |
apify call dennydevelops/wayback-change-history --silent --output-dataset

```

## MCP server setup

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

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/ClZaFca22wOpqMJrd/builds/WbbpXIEtfvCt7wWTY/openapi.json
