# Website Change Monitor & Page Diff Tracker (`joaosbp/website-change-monitor-seo-ai`) Actor

Monitor website, SEO, pricing, landing page, documentation, and AI/RAG content changes. Track persistent snapshots, detect significant changes, and export structured page diffs for Apify Schedules, APIs, webhooks, and automations.

- **URL**: https://apify.com/joaosbp/website-change-monitor-seo-ai.md
- **Developed by:** [João Victor](https://apify.com/joaosbp) (community)
- **Categories:** SEO tools, Automation, Developer tools
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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 Change Monitor & Page Diff Tracker

**Monitor website, SEO, pricing, landing page, documentation, changelog, and AI/RAG source changes — then export clean structured diffs for automations.**

![Apify Actor](https://img.shields.io/badge/Apify-Actor-39ff14?style=for-the-badge) ![Website monitoring](https://img.shields.io/badge/Website-Change%20Monitor-blue?style=for-the-badge) ![SEO](https://img.shields.io/badge/SEO-Diff%20Tracker-purple?style=for-the-badge) ![API ready](https://img.shields.io/badge/API-Ready-black?style=for-the-badge)

This Actor turns any public URL list into a scheduled website change monitoring API with persistent snapshots, noise filtering, and machine-readable diffs. It is built for teams that need automation-ready data — not just visual screenshots.

### ⚡ At a glance

| Monitor | Detect | Automate |
|---|---|---|
| Pricing pages | Text, HTML, metadata, headings, links | Apify Schedules |
| SEO pages | Significant changes and diff text | Webhooks |
| Landing pages | Added/removed content | Make, n8n, Zapier |
| Documentation | AI/RAG source drift | Slack, email, dashboards |

### 🎯 Best for

| Audience | Why they use it |
|---|---|
| **SEO agencies** | Monitor titles, descriptions, headings, landing pages, client sites, and competitor pages. |
| **SaaS founders & growth teams** | Track competitor pricing, homepage positioning, feature pages, docs, and changelogs. |
| **AI/RAG teams** | Know when source docs, help centers, FAQs, or knowledge-base pages changed. |
| **Marketing teams** | Watch CTAs, offers, testimonials, campaign pages, and product messaging. |
| **Automation builders** | Feed clean JSON diffs into alerts, dashboards, BI pipelines, and AI agents. |

### What this Actor does

1. Takes a list of public URLs.
2. Fetches each page with fast HTTP mode by default.
3. Optionally renders JavaScript-heavy pages with Playwright browser mode.
4. Extracts visible text, HTML, or metadata.
5. Removes noisy page parts such as scripts, navbars, footers, cookie banners, dates, or custom regex patterns.
6. Stores a persistent baseline snapshot in a named key-value store.
7. Compares future runs against the previous snapshot.
8. Outputs structured dataset rows with `FIRST_SEEN`, `UNCHANGED`, `CHANGED`, or `ERROR` status.
9. Reports diff text, severity, change ratio, headings, link count, hashes, timestamps, and snapshot keys.
10. Works with Apify Schedules, Tasks, API, webhooks, and integrations.

### 🧭 Typical workflow

| Step | Action | Output |
|---:|---|---|
| 1 | Run with your URL list | `FIRST_SEEN` baseline snapshots |
| 2 | Schedule the same input | recurring checks |
| 3 | Detect changed pages | `CHANGED`, `severity`, `changeRatio` |
| 4 | Send results to your workflow | webhook, Slack, Make, n8n, Zapier, API |

### Why this Actor is different

Generic website monitors often create noisy alerts from cookie banners, timestamps, layout shifts, nav menus, ads, analytics widgets, and formatting changes. This Actor is designed for commercial automation workflows:

- **Persistent snapshots** across scheduled runs.
- **Structured JSON output** for API users, BI, automations, and AI agents.
- **CSS selector targeting** to monitor only `main`, `article`, `.pricing`, `#content`, or any page region.
- **Noise filtering** with excluded selectors, date/number handling, whitespace normalization, and regex ignores.
- **SEO-friendly modes** for visible text, stricter HTML comparison, or metadata-only monitoring.
- **Cost-aware defaults**: HTTP mode, proxy disabled, browser disabled, bounded concurrency, response-size caps.
- **Marketplace-safe security**: private/internal URLs are blocked by default to reduce SSRF risk.

### Use cases

| Use case | What to monitor | Recommended settings |
|---|---|---|
| Competitor pricing monitor | Pricing pages, plan tables, discount text | `contentSelector: "main"`, `ignoreNumbers: false`, `emitMode: "CHANGES_ONLY"` |
| SEO content monitor | Landing pages, service pages, blog pages | `extractionMode: "VISIBLE_TEXT"`, `ignoreDates: true` |
| SEO metadata tracker | Titles, descriptions, headings, links | `extractionMode: "METADATA"` |
| Landing page CTA monitor | Headlines, buttons, offers, hero text | `contentSelector: "main"`, low threshold such as `0.03` |
| Documentation monitor | Docs, developer portals, changelogs | `contentSelector: "main"`, `emitMode: "SIGNIFICANT_ONLY"` |
| AI/RAG freshness monitor | Knowledge-base pages, FAQs, help centers | scheduled task + webhook when pages change |
| Agency alert workflow | Client/competitor URL sets | separate `snapshotStoreName` per client |
| Compliance/evidence workflow | Public policy pages, terms, docs | enable `saveHtmlSnapshot` if raw HTML evidence is needed |

### Quick start

1. Open the Actor on Apify.
2. Add one or more URLs in **Pages to monitor**.
3. Keep `snapshotStoreName` stable across runs.
4. Run once to create the baseline. Results will be `FIRST_SEEN`.
5. Run again later, or create a Schedule. Results will be `UNCHANGED` or `CHANGED`.
6. Use `emitMode = CHANGES_ONLY` or `SIGNIFICANT_ONLY` for alerts.

Minimal input:

```json
{
  "startUrls": [{ "url": "https://example.com" }],
  "snapshotStoreName": "my-website-monitor",
  "emitMode": "ALL"
}
````

### Tutorial: create a daily competitor pricing monitor

#### Step 1 — Add competitor URLs

```json
{
  "startUrls": [
    { "url": "https://competitor-one.com/pricing" },
    { "url": "https://competitor-two.com/pricing" },
    { "url": "https://competitor-three.com/pricing" }
  ],
  "snapshotStoreName": "competitor-pricing-daily-monitor",
  "contentSelector": "main",
  "extractionMode": "VISIBLE_TEXT",
  "ignoreDates": true,
  "ignoreNumbers": false,
  "significantChangeThreshold": 0.03,
  "emitMode": "CHANGES_ONLY",
  "maxConcurrency": 3,
  "useBrowser": false,
  "proxy": { "useApifyProxy": false }
}
```

#### Step 2 — Run once to create the baseline

The first run returns `FIRST_SEEN` because no previous snapshot exists yet.

#### Step 3 — Save as an Apify Task

Create a Task after verifying the input. Use a different `snapshotStoreName` for each client, competitor group, or project.

#### Step 4 — Schedule the Task

Create an Apify Schedule, for example once per day. For high-priority pricing pages, daily monitoring is usually enough. For docs and SEO content, daily or weekly monitoring usually works well.

#### Step 5 — Add notifications or workflow automation

Connect the Actor or Task to:

- Apify webhooks;
- Slack or Discord alerts;
- Make, Zapier, or n8n workflows;
- Google Sheets or Airtable dashboards;
- internal BI pipelines;
- AI agents that summarize changes.

### Example inputs

#### Example 1 — SEO metadata monitor

```json
{
  "startUrls": [
    { "url": "https://example.com" },
    { "url": "https://example.com/pricing" },
    { "url": "https://example.com/blog" }
  ],
  "snapshotStoreName": "seo-metadata-monitor",
  "extractionMode": "METADATA",
  "emitMode": "ALL",
  "significantChangeThreshold": 0.01
}
```

#### Example 2 — Documentation and changelog monitor

```json
{
  "startUrls": [
    { "url": "https://docs.example.com/introduction" },
    { "url": "https://docs.example.com/api-reference" },
    { "url": "https://example.com/changelog" }
  ],
  "snapshotStoreName": "docs-rag-source-monitor",
  "contentSelector": "main",
  "excludeSelectors": ["nav", "footer", ".pagination", ".last-updated"],
  "extractionMode": "VISIBLE_TEXT",
  "ignoreDates": true,
  "significantChangeThreshold": 0.05,
  "emitMode": "SIGNIFICANT_ONLY"
}
```

#### Example 3 — Landing page CTA monitor

```json
{
  "startUrls": [
    { "url": "https://competitor.com" },
    { "url": "https://competitor.com/product" }
  ],
  "snapshotStoreName": "landing-page-cta-monitor",
  "contentSelector": "main",
  "excludeSelectors": ["script", "style", "nav", "footer", ".cookie", ".announcement-bar"],
  "ignoreDates": true,
  "ignoreNumbers": false,
  "significantChangeThreshold": 0.03,
  "emitMode": "CHANGES_ONLY"
}
```

#### Example 4 — Dry run without updating the stored baseline

```json
{
  "startUrls": [{ "url": "https://example.com/pricing" }],
  "snapshotStoreName": "production-pricing-monitor",
  "updateSnapshots": false,
  "emitMode": "ALL"
}
```

### Input reference

| Field | Type | Default | Description |
|---|---|---:|---|
| `startUrls` | array | `https://example.com` | Pages to monitor. |
| `snapshotStoreName` | string | `website-change-monitor-snapshots` | Named key-value store for previous snapshots. Use a different store per project/client. |
| `maxItems` | integer | `10` | Maximum pages processed in one run. |
| `maxConcurrency` | integer | `3` | Maximum URLs processed in parallel. Keep low for browser mode to control memory/cost. |
| `extractionMode` | enum | `VISIBLE_TEXT` | What to compare: `VISIBLE_TEXT`, `HTML`, or `METADATA`. |
| `contentSelector` | string | empty | Optional CSS selector for the exact page area to monitor, e.g. `main`, `.pricing`, `article`. |
| `excludeSelectors` | string\[] | common noise selectors | Elements removed before comparison. |
| `ignoreRegexes` | string\[] | `[]` | Regex patterns removed from content before comparison. |
| `ignoreWhitespace` | boolean | `true` | Normalize whitespace. |
| `ignoreCase` | boolean | `false` | Normalize casing. |
| `ignoreNumbers` | boolean | `false` | Normalize numbers. Keep disabled for pricing pages. |
| `ignoreDates` | boolean | `true` | Normalize common date formats. |
| `significantChangeThreshold` | number | `0.05` | Minimum changed-content ratio to mark a change significant. |
| `emitMode` | enum | `ALL` | `ALL`, `CHANGES_ONLY`, or `SIGNIFICANT_ONLY`. |
| `saveHtmlSnapshot` | boolean | `false` | Store raw HTML evidence in the snapshot store. Increases storage usage. |
| `updateSnapshots` | boolean | `true` | Disable for dry-run comparisons. |
| `maxDiffCharacters` | integer | `2000` | Maximum length of `addedText` / `removedText`. |
| `useBrowser` | boolean | `false` | Use Playwright for JavaScript-rendered pages. Costs more than HTTP mode. |
| `maxRetries` | integer | `2` | Retry attempts for transient fetch errors. Applies to HTTP and browser mode. |
| `requestTimeoutSecs` | integer | `20` | Fetch/browser timeout per page. |
| `maxContentBytes` | integer | `2000000` | Maximum bytes read per page before stopping to protect memory/storage cost. |
| `allowPrivateUrls` | boolean | `false` | Allow localhost/private/internal IPs. Keep disabled for public runs. |
| `proxy` | object | disabled | Optional Apify Proxy configuration. Most public pages do not need proxy. |

### Output

The Actor writes results to the default dataset. Each row represents one monitored URL.

#### Dataset item example — first run

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com",
  "title": "Example Domain",
  "statusCode": 200,
  "changeType": "FIRST_SEEN",
  "severity": "HIGH",
  "isSignificant": true,
  "changeRatio": 1,
  "previousHash": null,
  "currentHash": "4a415f5252c741fbf3b4e11711552f78d0a5c3c0abaeefacedae0fc0807b63db",
  "previousFetchedAt": null,
  "currentFetchedAt": "2026-07-05T18:44:29.407Z",
  "addedText": "Example Domain This domain is for use in documentation examples...",
  "removedText": null,
  "headings": ["Example Domain"],
  "linksCount": 1,
  "contentLength": 125,
  "snapshotKey": "SNAPSHOT_...",
  "htmlSnapshotKey": null,
  "source": "http",
  "error": null
}
```

#### Dataset item example — changed page

```json
{
  "url": "https://competitor.com/pricing",
  "finalUrl": "https://competitor.com/pricing",
  "title": "Pricing | Competitor",
  "statusCode": 200,
  "changeType": "CHANGED",
  "severity": "HIGH",
  "isSignificant": true,
  "changeRatio": 0.31,
  "previousHash": "old-hash",
  "currentHash": "new-hash",
  "previousFetchedAt": "2026-07-04T08:00:00.000Z",
  "currentFetchedAt": "2026-07-05T08:00:00.000Z",
  "addedText": "Pro plan $39 per month AI add-on included",
  "removedText": "Pro plan $29 per month",
  "headings": ["Pricing", "Pro"],
  "linksCount": 12,
  "contentLength": 4210,
  "snapshotKey": "SNAPSHOT_...",
  "htmlSnapshotKey": "HTML_...",
  "source": "http",
  "error": null
}
```

#### Run summary

A summary is stored in the default key-value store under `OUTPUT`:

```json
{
  "processed": 10,
  "emitted": 3,
  "changed": 2,
  "significant": 1,
  "unchanged": 7,
  "firstSeen": 1,
  "failed": 0,
  "snapshotStoreName": "competitor-pricing-monitor",
  "startedAt": "2026-07-05T08:00:00.000Z",
  "finishedAt": "2026-07-05T08:00:12.000Z"
}
```

### Understanding results

| Field | Meaning |
|---|---|
| `FIRST_SEEN` | No previous snapshot existed. The current page is stored as the baseline. |
| `UNCHANGED` | Current normalized content hash equals the previous hash. |
| `CHANGED` | Content changed. Check `changeRatio`, `severity`, `addedText`, and `removedText`. |
| `ERROR` | The page failed after retries. Error details are in `error`. |

| Severity | Approximate ratio | Meaning |
|---|---:|---|
| `NONE` | 0 | No content change. |
| `LOW` | < 5% | Small change. Often harmless. |
| `MEDIUM` | 5–25% | Meaningful content change. |
| `HIGH` | > 25% | Large change or first snapshot. |

### API usage

Run asynchronously:

```bash
curl -X POST \
  "https://api.apify.com/v2/actors/joaosbp~website-change-monitor-seo-ai/runs" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{ "url": "https://example.com" }],
    "snapshotStoreName": "example-monitor",
    "emitMode": "ALL"
  }'
```

Run synchronously and get dataset items:

```bash
curl -X POST \
  "https://api.apify.com/v2/actors/joaosbp~website-change-monitor-seo-ai/run-sync-get-dataset-items?format=json" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{ "url": "https://example.com" }],
    "snapshotStoreName": "example-monitor"
  }'
```

### FAQ

#### Is this a visual screenshot monitor?

No. It is a structured content and metadata change monitor. It focuses on text, HTML, metadata, hashes, and diffs that are useful for APIs, alerts, automation, SEO, and AI workflows.

#### Does it crawl an entire website automatically?

No. You provide the URLs to monitor. This keeps runs predictable, cheaper, and easier to schedule. If you need whole-site discovery, combine it with a sitemap extractor or a future sitemap-monitor Actor.

#### Why is the first run marked `FIRST_SEEN`?

The first run creates the baseline snapshot. Run the same task again later with the same `snapshotStoreName` to detect `UNCHANGED` or `CHANGED`.

#### How do I reduce noisy alerts?

Use `contentSelector` to focus on the important part of the page and `excludeSelectors` to remove navbars, footers, cookie banners, widgets, ads, or timestamps. Keep `ignoreDates = true` for pages with last-updated dates.

#### Can I monitor pricing pages?

Yes. Keep `ignoreNumbers = false`, use `contentSelector = "main"`, and set `emitMode = "CHANGES_ONLY"`. This is one of the primary use cases.

#### Can I monitor SEO titles and descriptions?

Yes. Use `extractionMode = "METADATA"` to focus on title, description, headings, and links.

#### Can I monitor JavaScript-heavy pages?

Yes, enable `useBrowser = true`. Browser mode costs more than HTTP mode, so use it only when the page content is not available in the initial HTML.

#### Do I need proxies?

Usually no. Proxy is disabled by default to keep costs low. Enable Apify Proxy only for pages that block normal HTTP requests or require proxy routing.

#### Are private/internal URLs allowed?

No, they are blocked by default for safety. Advanced users can set `allowPrivateUrls = true` only for trusted private monitoring jobs.

#### Does the Actor store raw HTML?

Only if `saveHtmlSnapshot = true`. By default it stores normalized snapshots and compact diffs to control storage costs.

#### Can I send alerts to Slack, email, Make, Zapier, or n8n?

Yes. Use Apify webhooks or run the Actor as a scheduled Task, then send dataset items or the `OUTPUT` summary to your automation tool.

#### What happens on errors?

One bad URL does not fail the entire run. The Actor emits an `ERROR` dataset item for that URL and continues with the rest.

### Pricing and cost guidance

Recommended Apify monetization model: **Pay Per Event** after enough production usage data is collected.

A real remote test on 8 public pricing/docs pages with HTTP mode and proxy disabled succeeded at roughly `$0.000996` total platform cost after tuning, or about `$0.000125` per successful page in that sample.

Suggested future paid events:

| Event | Suggested price | When to charge |
|---|---:|---|
| `page-checked` | $0.003–$0.006 | successful non-error page check |
| `change-detected` | $0.03–$0.10 | page changed above hash baseline |
| `significant-change-detected` | $0.05–$0.20 | change ratio passes threshold |

Do not charge for per-URL errors. Keep browser/proxy modes as advanced options because they increase platform cost.

### Limitations

- The Actor monitors the URLs you provide; it does not discover all pages on a website by default.
- JavaScript-heavy websites may require `useBrowser = true`, which costs more.
- Some websites block bots or require login; this Actor is designed for public pages.
- Text diff is optimized for concise monitoring signals, not full Git-style line-by-line review.
- Private/internal URLs are blocked by default for safety.
- First run always produces `FIRST_SEEN` because there is no baseline yet.

### Best practices

- Use `contentSelector` for noisy layouts.
- Use separate `snapshotStoreName` values per client, project, competitor set, or environment.
- Keep `ignoreNumbers = false` for pricing and metrics pages.
- Use `ignoreDates = true` for blogs, docs, and changelogs with timestamps.
- Use `emitMode = CHANGES_ONLY` or `SIGNIFICANT_ONLY` for alerting workflows.
- Use `updateSnapshots = false` when testing thresholds against an existing baseline.
- Start with HTTP mode. Enable browser/proxy only when needed.

### Changelog

See [CHANGELOG.md](CHANGELOG.md) for release history.

### Search terms

Website change monitor, website monitoring, page change detector, page diff tracker, SEO monitor, SEO change tracker, competitor monitoring, competitor pricing monitor, pricing page monitor, landing page monitor, content change detection, documentation monitor, changelog monitor, help center monitor, AI RAG monitor, website alerts, web page monitoring API, Apify website monitor, scheduled web monitoring, structured diff API.

### Support

If a page produces noisy changes, configure `contentSelector`, `excludeSelectors`, and `ignoreRegexes` before increasing the threshold. For pricing pages, keep numbers enabled. For documentation pages, usually monitor the `main` element and exclude navigation, footer, pagination, and last-updated widgets.

# Actor input Schema

## `startUrls` (type: `array`):

Public URLs to monitor. The Actor stores a snapshot for each URL and compares future runs against it.

## `snapshotStoreName` (type: `string`):

Named key-value store used to persist previous snapshots across runs. Use a different store per monitoring project.

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

Safety limit for how many URLs are processed in one run.

## `maxConcurrency` (type: `integer`):

Maximum number of URLs processed in parallel. Keep low for browser mode to reduce memory and compute cost.

## `extractionMode` (type: `string`):

Choose what should be compared. Visible text is best for most business monitoring. HTML is stricter. Metadata compares title, description, headings, and links.

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

Optional CSS selector to monitor only one part of the page, such as main, article, .pricing, or #content. Leave empty to monitor the body.

## `excludeSelectors` (type: `array`):

CSS selectors removed before comparison. Use this for navbars, footers, cookie banners, ads, timestamps, or dynamic widgets.

## `ignoreRegexes` (type: `array`):

Advanced: regex patterns removed from content before comparison. Useful for dates, counters, session IDs, ad slots, or personalization noise.

## `ignoreWhitespace` (type: `boolean`):

Collapse repeated whitespace before comparison so formatting-only changes do not create noisy alerts.

## `ignoreCase` (type: `boolean`):

Convert content to lowercase before comparison so capitalization-only changes are ignored.

## `ignoreNumbers` (type: `boolean`):

Replace numbers with a placeholder before comparison. Keep disabled when monitoring prices, quantities, metrics, or version numbers.

## `ignoreDates` (type: `boolean`):

Replace common date formats with a placeholder before comparison so last-updated timestamps do not create noisy alerts.

## `significantChangeThreshold` (type: `number`):

Minimum changed-content ratio required to mark a change as significant. 0.05 means roughly 5% of comparable text changed.

## `emitMode` (type: `string`):

ALL writes a row for every monitored URL. CHANGES\_ONLY writes first-seen and changed pages. SIGNIFICANT\_ONLY writes only significant changes and errors.

## `saveHtmlSnapshot` (type: `boolean`):

Store raw HTML in the snapshot key-value store. Useful for evidence, but increases storage usage.

## `updateSnapshots` (type: `boolean`):

Disable for dry runs. If disabled, the Actor compares against current stored snapshots without replacing them.

## `maxDiffCharacters` (type: `integer`):

Maximum number of characters kept in addedText and removedText fields to keep dataset rows compact.

## `useBrowser` (type: `boolean`):

Render pages with Playwright for JavaScript-heavy sites. Disabled by default to keep runs fast and cheap.

## `maxRetries` (type: `integer`):

Number of retry attempts for transient network, timeout, 429, or 5xx errors.

## `requestTimeoutSecs` (type: `integer`):

Maximum time in seconds to wait for each page request or browser navigation.

## `maxContentBytes` (type: `integer`):

Maximum response size read per URL. Default supports most modern pricing/docs pages while still stopping very large responses to protect memory and storage costs.

## `allowPrivateUrls` (type: `boolean`):

Allow localhost, private IP ranges, and internal DNS targets. Keep disabled for public scraping to reduce SSRF risk.

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

Optional Apify Proxy configuration. Most public pages do not need proxy.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://example.com"
    }
  ],
  "snapshotStoreName": "website-change-monitor-snapshots",
  "maxItems": 10,
  "maxConcurrency": 3,
  "extractionMode": "VISIBLE_TEXT",
  "excludeSelectors": [
    "script",
    "style",
    "noscript",
    "svg",
    "iframe",
    "nav",
    "footer",
    "[aria-hidden=\"true\"]",
    ".cookie",
    ".cookies",
    "#cookie",
    "#cookies"
  ],
  "ignoreRegexes": [],
  "ignoreWhitespace": true,
  "ignoreCase": false,
  "ignoreNumbers": false,
  "ignoreDates": true,
  "significantChangeThreshold": 0.05,
  "emitMode": "ALL",
  "saveHtmlSnapshot": false,
  "updateSnapshots": true,
  "maxDiffCharacters": 2000,
  "useBrowser": false,
  "maxRetries": 2,
  "requestTimeoutSecs": 20,
  "maxContentBytes": 2000000,
  "allowPrivateUrls": false,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

Structured change detection results.

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

Counts of processed, emitted, changed, significant, unchanged, first-seen, and failed pages.

# 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 = {
    "startUrls": [
        {
            "url": "https://example.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("joaosbp/website-change-monitor-seo-ai").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 = { "startUrls": [{ "url": "https://example.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("joaosbp/website-change-monitor-seo-ai").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://example.com"
    }
  ]
}' |
apify call joaosbp/website-change-monitor-seo-ai --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=joaosbp/website-change-monitor-seo-ai",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Website Change Monitor & Page Diff Tracker",
        "description": "Monitor website, SEO, pricing, landing page, documentation, and AI/RAG content changes. Track persistent snapshots, detect significant changes, and export structured page diffs for Apify Schedules, APIs, webhooks, and automations.",
        "version": "0.1",
        "x-build-id": "BJ3sMwP4S5mpc9f9q"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/joaosbp~website-change-monitor-seo-ai/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-joaosbp-website-change-monitor-seo-ai",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/joaosbp~website-change-monitor-seo-ai/runs": {
            "post": {
                "operationId": "runs-sync-joaosbp-website-change-monitor-seo-ai",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/joaosbp~website-change-monitor-seo-ai/run-sync": {
            "post": {
                "operationId": "run-sync-joaosbp-website-change-monitor-seo-ai",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "startUrls"
                ],
                "properties": {
                    "startUrls": {
                        "title": "Pages to monitor",
                        "type": "array",
                        "description": "Public URLs to monitor. The Actor stores a snapshot for each URL and compares future runs against it.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "snapshotStoreName": {
                        "title": "Snapshot store name",
                        "type": "string",
                        "description": "Named key-value store used to persist previous snapshots across runs. Use a different store per monitoring project.",
                        "default": "website-change-monitor-snapshots"
                    },
                    "maxItems": {
                        "title": "Maximum pages per run",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Safety limit for how many URLs are processed in one run.",
                        "default": 10
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Maximum number of URLs processed in parallel. Keep low for browser mode to reduce memory and compute cost.",
                        "default": 3
                    },
                    "extractionMode": {
                        "title": "Extraction mode",
                        "enum": [
                            "VISIBLE_TEXT",
                            "HTML",
                            "METADATA"
                        ],
                        "type": "string",
                        "description": "Choose what should be compared. Visible text is best for most business monitoring. HTML is stricter. Metadata compares title, description, headings, and links.",
                        "default": "VISIBLE_TEXT"
                    },
                    "contentSelector": {
                        "title": "Content CSS selector",
                        "type": "string",
                        "description": "Optional CSS selector to monitor only one part of the page, such as main, article, .pricing, or #content. Leave empty to monitor the body."
                    },
                    "excludeSelectors": {
                        "title": "Exclude CSS selectors",
                        "type": "array",
                        "description": "CSS selectors removed before comparison. Use this for navbars, footers, cookie banners, ads, timestamps, or dynamic widgets.",
                        "default": [
                            "script",
                            "style",
                            "noscript",
                            "svg",
                            "iframe",
                            "nav",
                            "footer",
                            "[aria-hidden=\"true\"]",
                            ".cookie",
                            ".cookies",
                            "#cookie",
                            "#cookies"
                        ],
                        "items": {
                            "type": "string"
                        }
                    },
                    "ignoreRegexes": {
                        "title": "Ignore regex patterns",
                        "type": "array",
                        "description": "Advanced: regex patterns removed from content before comparison. Useful for dates, counters, session IDs, ad slots, or personalization noise.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "ignoreWhitespace": {
                        "title": "Ignore whitespace changes",
                        "type": "boolean",
                        "description": "Collapse repeated whitespace before comparison so formatting-only changes do not create noisy alerts.",
                        "default": true
                    },
                    "ignoreCase": {
                        "title": "Ignore letter casing",
                        "type": "boolean",
                        "description": "Convert content to lowercase before comparison so capitalization-only changes are ignored.",
                        "default": false
                    },
                    "ignoreNumbers": {
                        "title": "Ignore number changes",
                        "type": "boolean",
                        "description": "Replace numbers with a placeholder before comparison. Keep disabled when monitoring prices, quantities, metrics, or version numbers.",
                        "default": false
                    },
                    "ignoreDates": {
                        "title": "Ignore date changes",
                        "type": "boolean",
                        "description": "Replace common date formats with a placeholder before comparison so last-updated timestamps do not create noisy alerts.",
                        "default": true
                    },
                    "significantChangeThreshold": {
                        "title": "Significant change threshold",
                        "minimum": 0,
                        "maximum": 1,
                        "type": "number",
                        "description": "Minimum changed-content ratio required to mark a change as significant. 0.05 means roughly 5% of comparable text changed.",
                        "default": 0.05
                    },
                    "emitMode": {
                        "title": "Dataset output mode",
                        "enum": [
                            "ALL",
                            "CHANGES_ONLY",
                            "SIGNIFICANT_ONLY"
                        ],
                        "type": "string",
                        "description": "ALL writes a row for every monitored URL. CHANGES_ONLY writes first-seen and changed pages. SIGNIFICANT_ONLY writes only significant changes and errors.",
                        "default": "ALL"
                    },
                    "saveHtmlSnapshot": {
                        "title": "Save HTML snapshots",
                        "type": "boolean",
                        "description": "Store raw HTML in the snapshot key-value store. Useful for evidence, but increases storage usage.",
                        "default": false
                    },
                    "updateSnapshots": {
                        "title": "Update stored snapshots",
                        "type": "boolean",
                        "description": "Disable for dry runs. If disabled, the Actor compares against current stored snapshots without replacing them.",
                        "default": true
                    },
                    "maxDiffCharacters": {
                        "title": "Maximum diff characters",
                        "minimum": 100,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Maximum number of characters kept in addedText and removedText fields to keep dataset rows compact.",
                        "default": 2000
                    },
                    "useBrowser": {
                        "title": "Render pages with browser",
                        "type": "boolean",
                        "description": "Render pages with Playwright for JavaScript-heavy sites. Disabled by default to keep runs fast and cheap.",
                        "default": false
                    },
                    "maxRetries": {
                        "title": "Maximum retries",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "integer",
                        "description": "Number of retry attempts for transient network, timeout, 429, or 5xx errors.",
                        "default": 2
                    },
                    "requestTimeoutSecs": {
                        "title": "Request timeout seconds",
                        "minimum": 5,
                        "maximum": 120,
                        "type": "integer",
                        "description": "Maximum time in seconds to wait for each page request or browser navigation.",
                        "default": 20
                    },
                    "maxContentBytes": {
                        "title": "Max content bytes",
                        "minimum": 50000,
                        "maximum": 5000000,
                        "type": "integer",
                        "description": "Maximum response size read per URL. Default supports most modern pricing/docs pages while still stopping very large responses to protect memory and storage costs.",
                        "default": 2000000
                    },
                    "allowPrivateUrls": {
                        "title": "Allow private/internal URLs",
                        "type": "boolean",
                        "description": "Allow localhost, private IP ranges, and internal DNS targets. Keep disabled for public scraping to reduce SSRF risk.",
                        "default": false
                    },
                    "proxy": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Optional Apify Proxy configuration. Most public pages do not need proxy.",
                        "default": {
                            "useApifyProxy": false
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
