# Website Content Change Monitor — reliable diffs & alerts (`korado_labs/content-change-monitor`) Actor

Monitor any web page for content changes with a CSS selector or whole-page text. Get a unified diff, added/removed lines, previous & current content, and webhook alerts. Export data, run via API, schedule runs, or integrate with other tools.

- **URL**: https://apify.com/korado\_labs/content-change-monitor.md
- **Developed by:** [Korado Labs](https://apify.com/korado_labs) (community)
- **Categories:** Automation, Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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.
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?

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

## Website Content Change Monitor 🔔

Monitor any web page for **content changes** — a price, a changelog, a policy, a
job listing, a competitor's copy — and get a clean **diff** the moment it changes.
Point it at a URL (optionally a CSS selector), and every run compares the page to
the previous snapshot and tells you exactly what changed. Export data, run via
API, schedule runs, or integrate with other tools.

### What can this monitor do?

- 🔁 **Detect changes across runs** — snapshots persist in a named key-value
  store, so scheduled runs catch changes between checks (not just within one run).
- 🎯 **Watch a region or the whole page** — target a CSS selector, or monitor the
  full page text.
- 🧮 **Real diffs** — unified diff plus added/removed lines, previous content and
  current content, and a stable content hash.
- 🧱 **Reliable by design** — HTTP-first with proxy retry; a missing selector
  becomes a clean, explained state (optionally whole-page fallback) instead of a
  failed run. One bad page never aborts the batch.
- 📣 **Alerts** — POST every changed page to a webhook; include an email address
  in the record for an Apify email integration.
- 🚫 **No false positives** — text mode normalizes whitespace/markup so cosmetic
  reflows don't read as changes.

### What data do I get per page?

| Field | Description |
|---|---|
| `url`, `name` | The page and its label |
| `changed` | `true` if content changed since the last run (`null` on first run/errors) |
| `firstRun` | `true` the first time a page is seen (baseline, no diff yet) |
| `diff` | `{ addedLines, removedLines, addedCount, removedCount, unifiedDiff }` |
| `previousContent`, `currentContent` | Old and new extracted content |
| `currentHash`, `previousHash` | SHA-256 content hashes |
| `status` | `ok`, `error`, or `selector_not_found` |
| `checkedAt`, `lastChangedAt` | ISO timestamps |
| `matchedNodes`, `usedWholePageFallback` | Selector diagnostics |

### How much does it cost?

Pay-per-event, **nothing on a failed/blocked fetch**:

| Event | Price |
|---|---|
| Page checked | $0.002 |
| Change detected | $0.01 |

Monitoring 100 pages daily where 5 change ≈ `100 × $0.002 + 5 × $0.01 = $0.25/day`.

### How do I use it?

1. Add the pages you want to watch (URL + optional `contentSelector`).
2. Run once to capture the **baseline** (`firstRun: true`, no diff).
3. **Schedule** it (hourly/daily). Each subsequent run reports what changed.
4. Set `onlyChanges: true` and a `webhookUrl` to get pinged only when something moves.

#### Input

```json
{
  "pages": [
    { "url": "https://apify.com/change-log", "contentSelector": "main", "name": "Apify changelog" },
    { "url": "https://example.com/pricing", "contentSelector": ".price" }
  ],
  "onlyChanges": true,
  "webhookUrl": "https://hooks.example.com/notify"
}
```

Single-page (compatible with other content checkers):

```json
{ "url": "https://www.apify.com/change-log", "contentSelector": ".change-log ul:nth-of-type(1)", "sendNotificationTo": "you@company.com" }
```

#### Output (a changed page)

```json
{
  "url": "https://example.com/pricing",
  "changed": true,
  "firstRun": false,
  "diff": { "addedCount": 1, "removedCount": 1, "addedLines": ["$29 / mo"], "removedLines": ["$19 / mo"], "unifiedDiff": "…" },
  "previousContent": "Pro plan $19 / mo",
  "currentContent": "Pro plan $29 / mo",
  "checkedAt": "2026-08-26T10:00:00Z",
  "lastChangedAt": "2026-08-26T10:00:00Z"
}
```

### What can you do with the data?

- 💲 **Price & promo monitoring** — alert when a competitor changes pricing.
- 📝 **Changelog / policy / ToS tracking** — know the moment terms change.
- 📣 **Competitor copy & positioning** — watch landing pages and messaging.
- 💼 **Listings & availability** — jobs, tenders, stock, real-estate pages.
- 🤖 **Trigger workflows** — webhook straight into Slack, Make, Zapier, or your app.

### FAQ

**How are changes detected across runs?** Each page's normalized content is
hashed and stored in a named key-value store (`stateStore`). The next run compares
against it. Use distinct `stateStore` names to run independent monitors.

**What if my CSS selector stops matching?** By default `wholePageFallback` keeps
the run healthy by monitoring the whole page text; set it to `false` to instead
get an explicit `selector_not_found` status.

**Does it handle JavaScript-rendered pages?** It fetches server-rendered HTML
(fast and reliable for most monitored content). For heavily client-rendered
regions, target a selector present in the initial HTML or an underlying data URL.

**Text or HTML comparison?** Text by default (ignores cosmetic markup/whitespace).
Set `extractHtml: true` to compare raw inner HTML.

**Will a failed fetch cost me?** No. Only successful checks and detected changes
are billed.

**Can I get only the pages that changed?** Yes — set `onlyChanges: true`.

**Can I run it on a schedule and via API?** Yes — schedule it in Apify or call it
via the Apify API/SDK like any actor.

**Is scraping public pages legal?** You're responsible for complying with each
site's terms and applicable law. This actor reads publicly accessible pages only.

# Actor input Schema

## `pages` (type: `array`):

List of pages to check. Each item is a URL, or an object {url, contentSelector, name}. contentSelector is a CSS selector limiting the monitored region (omit to monitor the whole page).

## `url` (type: `string`):

Monitor a single page. Use `pages` for multiple. Kept for easy migration from other content checkers.

## `contentSelector` (type: `string`):

CSS selector for the region to watch on the single `url`. Leave empty to watch the whole page.

## `wholePageFallback` (type: `boolean`):

If the CSS selector matches nothing, monitor the whole page's text instead of erroring. Keeps runs reliable when a site changes its markup.

## `extractHtml` (type: `boolean`):

Compare raw inner HTML rather than normalized visible text. Text mode (default) ignores cosmetic whitespace/markup changes.

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

Only push records for pages that actually changed since the last run (great for scheduled runs + webhooks).

## `webhookUrl` (type: `string`):

If set, each changed page is POSTed as JSON to this URL.

## `sendNotificationTo` (type: `string`):

Email to notify on change. Delivered via an Apify integration/webhook configured on this actor; the address is included in the change record.

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

Named key-value store that holds the previous snapshots so changes are detected across runs. Use distinct names to run independent monitors.

## `proxy` (type: `object`):

Proxy used for fetching. Recommended for anti-bot sites.

## Actor input object example

```json
{
  "pages": [
    {
      "url": "https://apify.com/change-log",
      "contentSelector": "main",
      "name": "Apify changelog"
    }
  ],
  "url": "https://www.apify.com/change-log",
  "contentSelector": ".change-log ul:nth-of-type(1)",
  "wholePageFallback": true,
  "extractHtml": false,
  "onlyChanges": false,
  "stateStore": "content-snapshots",
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One record per checked page: changed flag, diff, previous and current content.

# 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 = {
    "pages": [
        {
            "url": "https://apify.com/change-log",
            "contentSelector": "main",
            "name": "Apify changelog"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("korado_labs/content-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 = { "pages": [{
            "url": "https://apify.com/change-log",
            "contentSelector": "main",
            "name": "Apify changelog",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("korado_labs/content-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 '{
  "pages": [
    {
      "url": "https://apify.com/change-log",
      "contentSelector": "main",
      "name": "Apify changelog"
    }
  ]
}' |
apify call korado_labs/content-change-monitor --silent --output-dataset

```

## MCP server setup

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