# Website Change Monitor & Diff Alerts (`danielmichalek/website-change-monitor`) Actor

Watch pages (terms, price lists, competitor sites, documentation) and report what changed between runs with a normalized text diff and severity.

- **URL**: https://apify.com/danielmichalek/website-change-monitor.md
- **Developed by:** [Daniel Michałek](https://apify.com/danielmichalek) (community)
- **Stats:** 2 total users, 1 monthly users, 33.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page checkeds

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/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 Monitor & Diff Alerts

Watch any list of public pages — competitor pricing, terms and conditions, public price lists, documentation, job pages, regulatory notices — and get a precise answer to one question every run: **what changed since last time?** The Actor stores a normalized text snapshot of every page in a named key-value store, compares the current page with it and reports `NEW`, `UNCHANGED`, `MINOR`, `CHANGED` or `ERROR` together with the added and removed lines.

### What it does

- Fetches each URL with timeouts, retries and polite per-host delays; no browser needed for server-rendered pages.
- Extracts the main content: scripts, styles, navigation, header, footer, sidebars and cookie banners are removed automatically, or you pass a CSS selector such as `main` or `#pricing`.
- Normalizes whitespace and removes volatile fragments you define as regular expressions (dates, counters, timestamps) so they never trigger false alerts.
- Compares against the previous snapshot with a line diff and classifies the result; `MINOR` versus `CHANGED` is controlled by a configurable threshold.
- Saves the new snapshot, pushes one dataset item per page and writes a Markdown `REPORT` with changed pages first, including the added and removed lines.
- Works with Apify schedules and integrations: run it hourly or daily and connect the dataset to Slack, e-mail, Make, Zapier or a webhook; filter on `status`.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| urls | array of strings | required | Pages to watch (max 2000 per run). |
| selector | string | "" | CSS selector to scope the comparison. |
| ignorePatterns | array of strings | \[] | Regular expressions removed before comparing (dates, counters). |
| minorThreshold | number | 0.02 | Changes below this share of lines are `MINOR`. |
| includeDiff | boolean | true | Include up to 20 added / 20 removed lines per changed page. |
| emitUnchanged | boolean | true | When false, unchanged pages are not pushed (and not billed). |
| stateStoreName | string | website-change-monitor-snapshots | Named key-value store holding snapshots (created by the Actor on the first run); one per schedule. If the store is not accessible under the Actor's limited permissions, the run uses its default store and reports `state.mode = default-fallback` in OUTPUT. |
| maxItems | integer | 200 | Hard cap on checked pages per run. |

### Output

One dataset item per page: `status`, `changedRatio`, `added`, `removed`, `addedSample`, `removedSample`, `title`, `titleChanged`, `wordCount`, `textHash`, `previousHash`, `previousCheckedAt`, `httpStatus` and `error` for failed fetches. The `overview` view lists changes first. The key-value store holds `REPORT` (Markdown) and `OUTPUT` with counts per status.

Example item (shortened):

```json
{
  "finalUrl": "https://competitor.example/pricing",
  "status": "CHANGED",
  "changedRatio": 0.18,
  "added": 2,
  "removed": 1,
  "addedSample": ["Pro: 35 EUR / month", "Annual plans available"],
  "removedSample": ["Pro: 30 EUR / month"],
  "previousCheckedAt": "2026-10-20T06:00:03.120Z"
}
```

### Pricing and cost control

Pay per event: **Page checked** (`page-checked`) — one event per page fetched in a run, including unchanged pages (set `emitUnchanged` to false to pay only for pages with news). `maxItems` caps the number of pages per run. A daily check of 100 pages costs 100 events per day and runs in about a minute at 256 MB.

### Limitations and fair use

- Server-rendered HTML only; content injected by JavaScript after load is not seen. Pages that are entirely client-rendered produce a low `wordCount` so you can spot them.
- Snapshots are capped at 200 KB of text per page; larger pages are compared on the first 200 KB.
- Only publicly reachable pages: no logins, paywalls or CAPTCHA solving, and robots directives are respected by not crawling beyond the URLs you list.
- The diff is textual; image or layout changes are not detected.

### Example

Input: three public pages with date patterns ignored. First run: three `NEW` items (snapshots created). Next run: `UNCHANGED` for untouched pages and `CHANGED` with the exact lines for a page whose price list moved. See `examples/input.json` and `examples/output.json`.

### Use cases

- Competitive intelligence: pricing, plans, feature pages, campaign landing pages.
- Compliance: terms of service, privacy policies, supplier conditions, insurance terms.
- Operations: public documentation and API changelogs that break integrations.
- Sales: career pages and "news" pages of target accounts as buying signals.

# Actor input Schema

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

URLs to monitor (terms pages, price lists, competitor pages, documentation). Max 2000 per run; one billable event per checked page.

## `selector` (type: `string`):

Limit the comparison to one part of the page, e.g. `main` or `#pricing`. When empty, navigation, header, footer, sidebars and cookie banners are removed automatically.

## `ignorePatterns` (type: `array`):

Regular expressions removed from the text before comparing, e.g. dates, visitor counters, timestamps: `\d{1,2}\.\d{1,2}\.\d{4}`.

## `minorThreshold` (type: `number`):

Changes affecting less than this share of lines are reported as MINOR instead of CHANGED (0.02 = 2%).

## `includeDiff` (type: `boolean`):

Add up to 20 added and 20 removed lines to each changed item and to the report.

## `emitUnchanged` (type: `boolean`):

When false, unchanged pages are counted in the report but not pushed to the dataset (and not billed).

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

Named key-value store holding the previous snapshots (created by this Actor on the first run). Use one name per schedule so monitors do not interfere. If the store cannot be accessed under the Actor's limited permissions, the run falls back to its default key-value store and reports this in OUTPUT.state.

## `maxItems` (type: `integer`):

Hard cap on checked pages (cost control).

## Actor input object example

```json
{
  "urls": [
    "https://apify.com/pricing"
  ],
  "selector": "",
  "ignorePatterns": [],
  "minorThreshold": 0.02,
  "includeDiff": true,
  "emitUnchanged": true,
  "stateStoreName": "website-change-monitor-snapshots",
  "maxItems": 200
}
```

# Actor output Schema

## `results` (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 = {
    "urls": [
        "https://apify.com/pricing"
    ],
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/website-change-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 = {
    "urls": ["https://apify.com/pricing"],
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("danielmichalek/website-change-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 '{
  "urls": [
    "https://apify.com/pricing"
  ],
  "maxItems": 200
}' |
apify call danielmichalek/website-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danielmichalek/website-change-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/VDivXCwFoNIrrIpJW/builds/US624YLqU2gHsomCs/openapi.json
