# Website Change Monitor – Page Change Detection & Diff (`ostrata/website-change-monitor`) Actor

Monitor web pages for changes: schedule it and get what changed since the last run (added / removed lines, similarity, title change). Watch a whole page or one CSS selector, ignore dates and counters. Plain HTTP, robots.txt respected, pay per page.

- **URL**: https://apify.com/ostrata/website-change-monitor.md
- **Developed by:** [Ostrata](https://apify.com/ostrata) (community)
- **Categories:** Automation, SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 changes

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 – Page Change Detection & Diff

**Know when a web page changes, and exactly what changed.** Give it a list of pages, schedule it (hourly, daily, weekly) and every run tells you which pages changed since the previous run, with the added and removed lines, a similarity score and title changes. Watch a whole page or only one part of it with a CSS selector.

**Also known as:** website change detection, page change monitor, web page diff, visualping / changedetection alternative, competitor price monitoring, terms of service change tracker, content change alerts, website monitoring API.

- **Simple and polite:** one plain HTTP request per page, robots.txt respected, at least 1.5 s between pages of the same site. No browser, no proxies, no bot-protection bypass.
- **Low-noise diffs:** navigation, footer and cookie banners are ignored by default; add regex ignore patterns for clocks, counters or copyright years; reordered lines are not counted as a change.
- **Memory between runs:** the last version of each page is kept in a named key-value store, so scheduled runs compare against the previous run. Use different store names for different watch lists.
- **Alerts anywhere:** connect the run to Slack, email, Google Sheets, Zapier, Make or n8n with Apify integrations, or call it from an AI agent through Apify MCP.

### Input

```json
{
  "urls": ["https://www.competitor.com/pricing", "https://www.example.com/terms"],
  "cssSelector": "main",
  "ignorePatterns": ["©\\s*\\d{4}", "\\d{1,2}:\\d{2}"],
  "onlyChanges": true,
  "stateStoreName": "my-competitors"
}
```

The first run of a page stores a **baseline** (status `baseline`). Later runs return `unchanged` or `changed`.

### Output

```json
{
  "url": "https://example.com/",
  "status": "changed",
  "changed": true,
  "checkedAt": "2026-10-19T10:27:34.900Z",
  "previousCheckedAt": "2026-10-18T10:27:22.800Z",
  "lastChangedAt": "2026-10-19T10:27:34.900Z",
  "title": "Example Domain",
  "titleChanged": false,
  "similarityPercent": 0,
  "addedLinesCount": 1,
  "removedLinesCount": 1,
  "addedLines": ["This domain is for use in documentation examples without needing permission. …"],
  "removedLines": ["This domain is for sale."],
  "wordCount": 24,
  "contentHash": "…",
  "httpStatus": 200
}
```

Pages that cannot be checked come back as free rows with `status: "error"` and a reason (`http_404`, `timeout`, `disallowed_by_robots_txt`, `selector_not_found`…), so you also notice when a page disappears. A `SUMMARY` record in the key-value store lists every page.

### Pricing

Pay per event, no subscription:

- **Page** (page checked: baseline or unchanged): **$0.0004** → $0.40 per 1,000 checks.
- **Change** (page checked and changed, with diff): **$0.003**.
- Actor start: $0.00005.

Each row is charged once, either as a Page or as a Change. Errors are free. With **Only output changed pages**, unchanged pages and baselines are neither output nor charged: you pay only for changes. The run stops at your maximum charge per run and never bills past it.

Example: 100 pages checked daily with Only changes on, 5 changes a day → about $0.45 per month.

### Honest limits

- **Static HTML only.** Content rendered by JavaScript in the browser is not seen; neither are pages behind a login. Sites that block bots (HTTP 403/429, challenge pages) are reported as errors, not bypassed.
- **Text changes, not visual changes.** Styling, images and layout are ignored. Screenshots are not taken.
- **The diff is line-based** (lines added / removed), not a word-level edit script. A long paragraph with one changed word shows as one removed and one added line.
- Dynamic bits (dates, counters, rotating testimonials, prices in A/B tests) can trigger changes: use `ignorePatterns` or a narrower `cssSelector`.
- Memory is per store name and per URL + selector; changing the selector starts a new baseline.

### Use cases

- Competitor pricing, plans and feature pages.
- Terms of service, privacy policy and legal notices (compliance).
- Product availability, release notes, documentation, job pages, government notices.
- Feeding agents with "what changed" instead of full pages.

### Related Actors

- [Accessibility Checker (WCAG, axe-core)](https://apify.com/ostrata/accessibility-checker): audit pages for WCAG 2.1 / 2.2 issues in real Chrome.
- [Wayback Machine API](https://apify.com/ostrata/wayback-machine-api): what a page looked like in the past and what changed between two dates.
- [EU Company Verification (KYB)](https://apify.com/ostrata/eu-company-check): verify companies from a name, SIREN/SIRET, VAT number or LEI.
- [Website to Company (Impressum & Mentions Légales)](https://apify.com/ostrata/website-company-check): find the legal entity behind a website and verify it.
- [France Company List (SIRENE)](https://apify.com/ostrata/france-company-list): French B2B company lists from the official register.
- [All-ATS Jobs API](https://apify.com/ostrata/ats-jobs-api): normalized jobs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee.
- [AI Brand Visibility Tracker](https://apify.com/ostrata/ai-search-visibility): whether ChatGPT, Perplexity and Gemini mention and cite your brand.

# Actor input Schema

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

One public page URL per line, e.g. a competitor's pricing page, terms of service, a product page, a job page.

## `cssSelector` (type: `string`):

Watch only this part of each page, e.g. main, #pricing, .price, article. Empty = the whole page body.

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

Text removed before comparing, to avoid false alerts: e.g. \d{1,2}:\d{2} (clocks), ©\s\*\d{4}, \d+ (people|views) (counters).

## `dropNavFooter` (type: `boolean`):

Removes <nav>, <footer> and common cookie-banner elements before comparing.

## `onlyChanges` (type: `boolean`):

For scheduled monitoring: unchanged pages, first-time baselines and errors are not output (and not charged).

## `includeText` (type: `boolean`):

Adds the full monitored text (max 50,000 characters) to each row.

## `maxDiffLines` (type: `integer`):

Added and removed lines listed per page (counts are always complete).

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

Named key-value store that remembers the last version of each page. Use different names for separate watch lists.

## `minDelayMsPerSite` (type: `integer`):

Politeness delay; a robots.txt Crawl-delay (max 10 s) is honoured if larger.

## Actor input object example

```json
{
  "urls": [
    "https://www.iana.org/help/example-domains",
    "https://apify.com/pricing"
  ],
  "dropNavFooter": true,
  "onlyChanges": false,
  "includeText": false,
  "maxDiffLines": 100,
  "stateStoreName": "website-change-monitor-state",
  "minDelayMsPerSite": 1500
}
```

# Actor output Schema

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

Dataset items.

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

Counts and per-page status.

# 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.iana.org/help/example-domains",
        "https://apify.com/pricing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ostrata/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://www.iana.org/help/example-domains",
        "https://apify.com/pricing",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ostrata/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://www.iana.org/help/example-domains",
    "https://apify.com/pricing"
  ]
}' |
apify call ostrata/website-change-monitor --silent --output-dataset

```

## MCP server setup

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