# Website Change Monitor: Text Diff, Markdown & Screenshot (`tamatama/website-change-monitor`) Actor

Website change monitor for a list of URLs: see exactly what changed since the last run as added and removed lines of page text, plus a content hash, clean Markdown and a screenshot per page. Schedule it to watch prices or docs. From $1.20 per 1,000 pages ($2 on the Free plan, less on paid plans).

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

## Pricing

from $1.20 / 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?

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: Text Diff, Markdown & Screenshot

**Website change monitor for anyone who needs to know when a web page changes** (competitor prices, product docs, terms of service, job or tender pages, sources of a RAG index): give it a list of URLs and get one row per page with **what changed since the last run** as added and removed lines of text, plus a content hash, the clean page text as Markdown and a screenshot. Schedule it and you have a change feed you can read in a spreadsheet, a webhook, an AI agent or a script.

Most screenshot tools give you a picture and leave the comparing to you. This Actor renders each page in a real Chromium browser, turns the main content into clean Markdown, and compares it with the version it saw last time, so a diff like `-Starter: $10` / `+Starter: $12` lands straight in your dataset.

### What you get for each page

A real row (the test page returns a new random id on every load, so the second run reports a change):

````json
{
  "url": "https://httpbin.org/uuid",
  "finalUrl": "https://httpbin.org/uuid",
  "httpStatus": 200,
  "status": "changed",
  "checkedAt": "2026-10-20T06:31:49.259881+00:00",
  "previousCheckedAt": "2026-10-20T06:31:44.787828+00:00",
  "contentHash": "f117a982ef6b9cbb9b6dc869ea08cd851f68d655d483d09b7d1f00053992f435",
  "addedLines": 1,
  "removedLines": 1,
  "diff": [
    "-\"uuid\": \"8342f2be-c43b-4f6f-a302-42b7607a2c94\"",
    "+\"uuid\": \"47b97d11-309a-493a-bb9a-973d21035e7b\""
  ],
  "wordCount": 6,
  "screenshotUrl": null,
  "screenshotError": null,
  "markdown": "```\n{\n\"uuid\": \"47b97d11-309a-493a-bb9a-973d21035e7b\"\n}\n```",
  "error": null
}
````

| Field | Meaning |
|---|---|
| `status` | `new` (first check under this monitor name), `changed`, `unchanged` or `error` |
| `diff` | Changed lines of the page text: `+` added, `-` removed (up to *Max diff lines*) |
| `addedLines`, `removedLines` | Complete counts, even when the diff is cut |
| `contentHash` | SHA-256 of the page text. Same text, same hash |
| `markdown` | Main content as Markdown: headings, lists, links, tables. Scripts, styles and images are left out |
| `screenshotUrl` | JPEG of the first screen (1280×800) or the full page, stored in the run's key-value store |
| `screenshotError` | Set when a full-page screenshot was too slow (then the first screen is saved) or failed; the text check still counts |
| `title`, `httpStatus`, `finalUrl` | Page title, HTTP status, address after redirects |
| `error` | Why a page could not be checked (blocked address, HTTP 404, timeout...). These rows are free |

The run's key-value store also has a `SUMMARY` record: pages checked, changed, new, unchanged and errors.

### How to use it in 5 minutes

1. Click **Try for free**. The form is filled with two example pages; press **Start**. Both come back as `new` in a few seconds.
2. Replace them with the pages you care about and pick a **Monitor name**, for example `competitor-pricing`.
3. Run it once to store the first version, then add a **Schedule** (daily or hourly) with the same input.
4. Every later run returns `changed` rows with the diff. Filter the dataset on `status = changed`, or connect an integration (email, Slack, webhook) that fires when the run finishes.

Tip: if a page shows a clock, a visitor counter or rotating offers, put their CSS selectors in **Ignore these elements**, or compare only the part you care about with **Compare only this part of the page** (e.g. `#pricing`). Otherwise every run reports a change.

### Input

| Field | Default | Description |
|---|---|---|
| `urls` | (required) | Public http/https pages, up to 100 per run |
| `monitorName` | `default` | The memory the next run compares against. Same name for the same watch list |
| `contentSelector` | main content | CSS selector of the part to compare |
| `ignoreSelectors` | none | CSS selectors removed before comparing |
| `maxDiffLines` | 200 | Diff lines kept per page (0 to 5,000) |
| `screenshot` | `viewport` | `viewport`, `fullPage` (cut at 16,000 px) or `none` |
| `includeMarkdown` | true | Add the page text to each row |
| `waitSeconds` | 0 | Extra wait after load for late content (0 to 15) |

### Pricing

Pay per event: **$2 per 1,000 pages checked** ($0.002 per page) on the Free plan, lower on paid Apify plans (Bronze $0.0018, Silver $0.0015, Gold and above $0.0012), plus a tiny start fee of $0.00005 per GB of memory per run (the default 1 GB counts as one). Platform usage is included. Pages that fail (blocked, 4xx/5xx, timeout) are not charged.

Example: watching 50 pages once a day for 30 days is 1,500 checks = **$3.00** on the Free plan.

### What it does not do

- **No pages behind a login**, no form filling, no CAPTCHA solving. Bot-protected pages may return a challenge page instead of the content; such a page is still compared as it was rendered.
- **Public pages only.** URLs that point to localhost, private networks (10.x, 192.168.x...), link-local or cloud metadata addresses are refused, also when a public page redirects there. This is a best-effort guard based on the DNS answer at check time, not a network firewall.
- **No notifications of its own.** Use Apify integrations or webhooks on the finished run.
- The comparison is on text, not on pixels: a changed image or color without a text change is not reported.
- The stored version is the first 300,000 characters of a page's text; a change beyond that point is caught by the hash but not shown in the diff.
- Two pages are loaded at once. Loading a page may take up to 30 seconds, plus up to 5 seconds for network activity to settle, your extra wait and the screenshot. Very large rows are cut: Markdown at 1,000,000 characters and each diff line at 2,000.

Please respect each site's terms of use when choosing pages to monitor.

### FAQ

**Where is the previous version stored?** In a named key-value store `page-snapshot-state` in your own Apify account, one record per page and monitor name. Nobody else can read it; delete it to start over.

**How do I call it from code, Make, n8n or Zapier?** Run the Actor with the Apify API or the official Apify apps for Make, n8n and Zapier, then read the dataset items. With the Apify MCP server, AI agents can call it as a tool. LangChain and LlamaIndex have Apify loaders for the dataset.

**Why did a page change although nothing visible changed?** Dynamic text (dates, counters, recommendations) is part of the page text. Exclude it with *Ignore these elements* or narrow the comparison with *Compare only this part of the page*.

**Can I get only the changed pages?** Read the dataset items and keep the rows with `status == "changed"`. All checked pages are returned so you also see `unchanged` and `error` rows.

### Related Actors by the same author

| Actor | What it does |
|---|---|
| [PDF to Markdown](https://apify.com/tamatama/doc-to-markdown) | PDF, Word, Excel and PowerPoint to Markdown and RAG chunks |
| [PDF OCR](https://apify.com/tamatama/image-pdf-ocr) | Scanned PDFs and images to text and a searchable PDF |
| [ATS Jobs](https://apify.com/tamatama/ats-jobs) | Job openings from company job boards (Greenhouse, Lever, Ashby and more) |
| [RemoteOK & Jobicy Remote Jobs API](https://apify.com/tamatama/remote-jobs) | Remote job listings from the Remote OK and Jobicy APIs |

### For AI Agents

Agents that keep a knowledge base fresh, watch a competitor or follow a changelog need one answer: "did this page change, and how?". This Actor returns exactly that as data (`status`, `diff`, `addedLines`, `removedLines`), so the agent reads a few lines instead of a whole page. It needs no login, is paid per page through Apify (pay per event) and runs with limited permissions.

**Apify MCP server**, restricted to this Actor:

```json
{
  "mcpServers": {
    "website-change-monitor": {
      "url": "https://mcp.apify.com?tools=tamatama/website-change-monitor"
    }
  }
}
```

Authorize with OAuth in the client, or pass `Authorization: Bearer <APIFY_TOKEN>`. A check is started through the tool `tamatama--website-change-monitor`, and `get-dataset-items` (added by the server) reads the rows when there are many pages. Example input for an agent that follows a changelog:

```json
{ "urls": ["https://example.com/changelog"], "monitorName": "changelog-watch", "screenshot": "none", "includeMarkdown": false }
```

Keep the same `monitorName` on every call: that name is what links a run to the previous version. The first call returns `status: "new"`; later calls return `changed` or `unchanged`, and `error` (with the reason in `error`) when a page could not be loaded.

**OpenAPI**: `https://api.apify.com/v2/acts/tamatama~website-change-monitor/builds/default/openapi.json`. **REST**: `POST https://api.apify.com/v2/acts/tamatama~website-change-monitor/run-sync-get-dataset-items` with the JSON input.

# Actor input Schema

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

Public web pages (http or https), up to 100 per run. Pages behind a login and private or internal network addresses are not supported.

## `monitorName` (type: `string`):

The Actor remembers the text of each page under this name and compares the next run against it. Use the same name in every scheduled run of the same watch list; use a new name to start fresh.

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

For example "#pricing" or ".terms-content". Leave empty to use the main content (main, then article, then the whole body).

## `ignoreSelectors` (type: `array`):

Elements removed before comparing, such as clocks, visitor counters, ads or rotating banners, e.g. ".timestamp", "#cookie-banner". This avoids changes that do not matter.

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

How many added or removed lines to include in the diff field. The counts addedLines and removedLines are always complete.

## `screenshot` (type: `string`):

First screen (1280×800), full page (cut at 16,000 px) or none. The image is saved as JPEG and linked in screenshotUrl.

## `includeMarkdown` (type: `boolean`):

Adds the clean page text (headings, lists, links and tables as Markdown) to each row. Turn off for smaller output when you only need the diff.

## `waitSeconds` (type: `integer`):

Wait this long after the page has loaded, for pages that fill in content late. 0 to 15.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/help/example-domains"
  ],
  "monitorName": "default",
  "maxDiffLines": 200,
  "screenshot": "viewport",
  "includeMarkdown": true,
  "waitSeconds": 0
}
```

# Actor output Schema

## `pages` (type: `string`):

No description

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

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

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

```

## MCP server setup

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