# Website Change Watcher: Page, Sitemap & Feed Monitor (`pavel.tashev/website-change-watcher`) Actor

Website change monitor and price tracker: watch any page, sitemap or feed and get only what changed. Track price and stock with CSS selectors (old, new, difference), quiet hours, no false alarms. Never charges for a failed or empty result.

- **URL**: https://apify.com/pavel.tashev/website-change-watcher.md
- **Developed by:** [Pavel Tashev](https://apify.com/pavel.tashev) (community)
- **Categories:** Automation, 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 target checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Website Change Watcher do?

**Website Change Watcher checks pages, sitemaps and feeds on a schedule and reports only what changed since the last run** — the added and removed text, the new and removed URLs, the new feed items, and the **before and after value of a price, stock status or any number you name**.

It is built for monitoring that runs unattended: competitor prices, product availability, pricing and terms pages, documentation, a competitor's new blog posts or job ads, and news feeds. Because it runs on the Apify platform you get scheduling, webhooks, integrations (Make, Zapier, n8n, Slack) and an API, and every run keeps its own history.

### Why use Website Change Watcher?

- **Values, not just text.** Point it at a price with a CSS selector and get `199.00 → 179.00`, with the difference and the percentage, classified as a price drop.
- **No false alarms.** Clocks, timestamps, "3 minutes ago" and request tokens are filtered out. A blocked page, a server error or a half-loaded sitemap keeps the saved snapshot instead of reporting an invented change.
- **Noise control.** Watch part of a page with a selector, ignore text with your own patterns, require a minimum percentage change, or hold a change back until it repeats.
- **Honest results.** Every row says exactly what happened: `ok`, `unchanged`, `baseline`, `empty`, `blocked`, `not_found`, `error` or `skipped`. **Only a real comparison is charged.**
- **Three kinds of target in one Actor.** A page's text, a sitemap's URLs, or a feed's items.

### How to monitor a website for changes

1. Click **Try for free**.
2. Put one or more addresses in **Targets**: a page, a sitemap, a feed, or a bare domain.
3. Optional: add **Values to extract** to track a price or stock status, and a **selector** to watch only part of the page.
4. Click **Start**. The first run saves a baseline and charges nothing.
5. Open **Schedules** and run the Actor every hour or every day. From then on each run returns only what changed.

### Price and stock monitoring

Add entries to **Values to extract**. Each one has a `name`, a CSS `selector` and a `type` (`text`, `number`, `price`, `date` or `boolean`):

```json
{
  "targets": ["https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"],
  "extract": [
    { "name": "price", "selector": ".price_color", "type": "price" },
    { "name": "inStock", "selector": ".instock.availability", "type": "boolean" },
    { "name": "title", "selector": "h1", "type": "text" }
  ]
}
```

The first run saves `{"price": 51.77, "inStock": true, "title": "A Light in the Attic"}`. When the price later drops to 46.99, the next run returns:

```json
{
  "status": "ok",
  "changed": true,
  "changeKind": "price-down",
  "values": { "price": 46.99, "inStock": true, "title": "A Light in the Attic" },
  "previousValues": { "price": 51.77, "inStock": true, "title": "A Light in the Attic" },
  "deltas": {
    "price": { "from": 51.77, "to": 46.99, "changed": true, "absolute": -4.78, "percent": -9.23 }
  }
}
```

`changeKind` tells you what moved, so a workflow can react differently to each case:

| changeKind | What it means |
|---|---|
| `price-down` / `price-up` | A value of type `price` moved down or up |
| `availability` | A value of type `boolean` flipped, for example in stock → sold out |
| `value` | Another extracted value changed |
| `content` | The page text changed, with no watched value moving |
| `structural` | A sitemap gained or lost URLs, or a feed gained items |

Availability wins over a price change, and a price drop wins over a price rise, so the most useful label is the one you get.

#### Selector syntax

Tags, `#id`, `.class`, `[attribute]`, `[attribute="value"]`, descendant paths (`.product .price`) and direct children (`.product > .price`). End a selector with `::text` (the default) or `::attr(name)` to read an attribute:

- `.price_color` — the text of the first matching element
- `meta[itemprop="price"]::attr(content)` — a price hidden in a meta tag, which is usually the most stable source
- `.product > .price::text` — only a direct child, ignoring nested matches
- `[data-testid="stock-status"]` — attribute-only selectors work too

Prices and numbers are read in any common format: `1,234.56`, `1.234,56`, `1 234,56`, `€19.99`, `19.99 EUR`. The currency is reported separately. Dates accept ISO, `17/09/2026` (day first), `17 September 2026` and similar.

#### When a selector finds nothing

The value is returned with `status: "empty"` and a reason such as `Nothing on the page matched ".price"`. It is never a silent `null`, and the saved snapshot is kept, so a redesign of the page does not look like a price change. If **every** watched value comes back empty, the whole target is reported as `empty` and is not charged.

### Scheduling: quiet hours, weekdays and a minimum gap

Schedule the Actor as often as you like and let these settings decide when a target is actually fetched. A skipped target is **not requested and not charged**.

- **Quiet hours** — for example `22:00-07:00`, including ranges that cross midnight.
- **Check only on these days** — for example `mon, tue, wed, thu, fri`.
- **Minimum hours between checks** — run the Actor hourly for urgent targets while a slower target is checked once a day.
- **Time zone** — a name such as `Europe/Sofia` or an offset such as `+02:00`. Defaults to UTC.

### Ignoring rotating content (debounce)

Some sites rotate banners or run A/B tests, so the page differs on every visit. Set **Report a change only after this many runs in a row** to 2 or 3: a change is held back until the same content is seen that many times, and the baseline is kept until then. The trade-off is that a genuine change is reported one run later.

### Input

| Field | Description | Default |
|---|---|---|
| **Targets** | Pages, sitemap URLs, feed URLs or domains. | – |
| **What to watch** | Detect automatically, or force page / sitemap / feed. | auto |
| **First check of a target** | Save the baseline silently, or return a baseline row. | baseline only |
| **Watch only this part of the page** | CSS selector: tags, `#id`, `.class`, `[attribute]`, paths with spaces or `>`. | whole page |
| **Values to extract** | Named values to track: `{name, selector, type}` with type `text`, `number`, `price`, `date` or `boolean`. | – |
| **Report a change only after this many runs in a row** | Hold a change back until it repeats, to ignore A/B tests. | 1 |
| **Quiet hours** | Window when targets are skipped, e.g. `22:00-07:00`. | – |
| **Check only on these days** | Weekdays when checks may run, e.g. `mon, tue`. | every day |
| **Minimum hours between checks** | Skip a target checked more recently than this. | 0 |
| **Time zone** | `Europe/Sofia` or `+02:00`, for quiet hours and weekdays. | UTC |
| **Compare visible text only** | Off also compares HTML, links and attributes. | on |
| **Ignore clocks, timestamps and "x minutes ago"** | Removes the usual per-visit noise. | on |
| **Ignore all numbers** | For view counters and stock levels. Turn off when watching prices. | off |
| **Ignore text matching** | Your own regular expressions, e.g. `Cart \(\d+\)`. | – |
| **Minimum change to report (%)** | Ignore changes smaller than this. | 0 |
| **Maximum URLs or items per target** | For sitemaps and feeds. | 50,000 |
| **Maximum characters of changed text** | Cap per target. | 5,000 |
| **Shrink safety threshold (%)** | Treat a suddenly tiny response as a block, not a change. | 30 |
| **Respect robots.txt** | Skip URLs the site disallows. | on |

Example input:

```json
{
  "targets": ["https://www.python.org/downloads/", "https://blog.cloudflare.com/rss/"],
  "selector": "main",
  "ignoreNumbers": false,
  "minChangePercent": 1,
  "quietHours": "22:00-07:00",
  "timezone": "Europe/Sofia",
  "requireConsecutiveChanges": 2
}
```

### Output

One row per target, whatever happened to it. A real example from a run on 17 September 2026:

```json
{
  "target": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "type": "page",
  "status": "ok",
  "reason": null,
  "changed": true,
  "changeKind": "price-down",
  "values": { "price": 46.99, "inStock": true, "title": "A Light in the Attic" },
  "previousValues": { "price": 51.77, "inStock": true, "title": "A Light in the Attic" },
  "deltas": {
    "price": { "from": 51.77, "to": 46.99, "changed": true, "absolute": -4.78, "percent": -9.23 }
  },
  "valueDetails": {
    "price": { "raw": "£46.99", "status": "ok", "selectorMatches": 1, "currency": "GBP" }
  },
  "similarity": 0.9861,
  "changePercent": 1.39,
  "title": "A Light in the Attic",
  "addedText": "£46.99",
  "removedText": "£51.77",
  "statusCode": 200,
  "previousCheckedAt": "2026-09-17T08:10:11+00:00",
  "checkedAt": "2026-09-17T09:10:09+00:00",
  "pendingChange": false,
  "error": null
}
```

For a sitemap you get `addedUrls`, `removedUrls` and `modifiedUrls`; for a feed you get `newItems` with title, URL and publication date. You can download the dataset as JSON, CSV, Excel or HTML, or fetch it through the API. A per-target summary of the run is saved in the key-value store as `WATCH_REPORT`.

### Output fields

| Field | Meaning |
|---|---|
| `target`, `url`, `site`, `type` | What you entered, what was read, and whether it was a page, sitemap or feed |
| `status`, `reason` | What happened to this target, and why when it is not `ok` or `unchanged` |
| `changed`, `changeKind` | Whether it changed, and what kind of change it was |
| `values`, `previousValues`, `deltas`, `valueDetails` | Extracted values now and before, the difference, and how each one was read |
| `pendingChange`, `pendingSeen` | A change is being held back until it repeats, and how many times it has been seen |
| `changeType` | Kept from version 1: `baseline`, `unchanged`, `modified` or `error` |
| `similarity`, `changePercent` | How much of the target changed |
| `added`, `removed`, `addedText`, `removedText`, `diff` | The changed text |
| `addedUrls`, `removedUrls`, `modifiedUrls` | Sitemap changes |
| `newItems` | New feed items (title, URL, date) |
| `title`, `selectorMatches`, `contentLength`, `previousContentLength`, `entries`, `previousEntries` | Context for the comparison |
| `statusCode`, `previousCheckedAt`, `checkedAt` | When and how the target answered |
| `error`, `note` | Why a target could not be compared, or what the run decided |

### What each status means

| status | Meaning | Charged? |
|---|---|---|
| `ok` | Checked, and something changed | Yes (plus the change fee) |
| `unchanged` | Checked, nothing changed | Yes |
| `baseline` | First check of this target; the snapshot was saved | No |
| `empty` | Fetched, but there was nothing to compare (selector or values matched nothing) | No |
| `blocked` | robots.txt, 401/403, rate limiting, or a page far smaller than last time | No |
| `not_found` | 404 or 410 | No |
| `error` | Anything else, with the reason in `reason` | No |
| `skipped` | Quiet hours, an excluded weekday, or the minimum gap between checks | No |

The run's status message lists the counts, for example `12 targets: 2 changed, 8 unchanged, 1 blocked, 1 skipped (2× price-down). Charged: 10 checks + 2 changes`.

### How much does it cost to monitor a website?

This Actor uses **pay-per-event pricing**: a small fee per target checked, plus a fee when a change is found. **Only a real comparison is charged**, which means `ok` and `unchanged`. A first check (`baseline`), a target that could not be read (`blocked`, `not_found`, `error`), a target with nothing to compare (`empty`) and a target skipped by your schedule (`skipped`) are all returned in the dataset **free of charge**. See the **Pricing** tab for current prices. Platform compute is included.

Each target is saved and charged the moment it finishes, not at the end of the run. If a run reaches your
maximum cost, or stops shortly before its timeout, the targets already checked keep their results and the
rest are returned as `skipped` free of charge. Nothing you paid for is thrown away.

#### Limits per run

| Limit | Value |
|---|---|
| Targets per run | 500 |
| Items per target (sitemap URLs or feed items) | up to 50,000 |
| Targets checked in parallel | up to 10 |
| Page size fetched per target | up to 10 MB |

These are the sizes the Actor is tested at. Larger watch lists work well as several scheduled runs.

Watching 20 pages every hour is 480 checks a day; watching them once a day is 20. Start with a daily schedule and increase it only for pages that really move.

### Where the comparison is stored

Snapshots live in a key-value store named `website-change-watcher-state` **in your own Apify account**, one compressed record per target. Nothing is stored outside your account. Deleting that store resets the monitoring, and the next run saves a fresh baseline. A snapshot is tied to the target plus the settings that decide what is compared (selector, text-only, ignore rules), so changing one of those starts a new baseline; changing reporting-only settings such as the minimum change does not.

### Tips

- **Use a selector for noisy pages.** `main`, `#content` or `.product` removes menus, cookie banners and "related posts".
- **Watching a price?** Add it to **Values to extract** as type `price` instead of relying on the text diff. Prefer a `meta[itemprop="price"]::attr(content)` selector when the page has one: it survives redesigns better than a visible element.
- **Alerted by rotating banners?** Set **Report a change only after this many runs in a row** to 2.
- **Checking many targets on one schedule?** Use **Minimum hours between checks** so urgent and slow targets can share a single hourly schedule.
- **Watching wording or availability?** Turn **Ignore all numbers** on so counters do not trigger alerts.
- **Getting an alert every run?** Add the changing phrase to **Ignore text matching**, or raise **Minimum change to report**.
- **JavaScript-rendered pages** are not supported: this Actor reads the HTML the server returns, with no browser. If the content only appears after scripts run, watch the underlying API endpoint or feed instead.

### For AI agents

The Actor runs with **limited permissions**, charges per event and needs no login, so an agent can call it through the Apify MCP server or the API and pay for it directly. A run is one call: pass `targets` (and `extract` for prices or stock), then read back one row per target with `status`, `changed`, `changeKind`, `deltas` and the text or URLs that changed. Because every row states its own `status`, an agent never has to guess whether an empty result means "no change" or "the page was blocked". Keep `firstRunOutput` at `none` for silent baselines, and schedule the Actor rather than polling it from the agent.

### FAQ and support

**Does it store the pages it reads?** Only a normalized, compressed copy of the compared text or URL list, in your own account, so the next run can diff against it. Email addresses are removed from the stored text and from the reported changes.

**Why does the first run report nothing?** The first check of a target saves a baseline. Set **First check of a target** to "Return a baseline row" if you want to confirm what is being watched.

**What happens when a site won't let us read its robots.txt?** Some sites put their `/robots.txt` behind bot protection and answer 403 or time out. We then can't tell what the site allows, so we follow one rule everywhere: **the address you entered is still read, and addresses we would have worked out ourselves are not.** A site with no robots.txt at all (404) is treated as allowing everything. For this Actor that means the URL you are watching is still checked, while sitemap and feed addresses we would look for on a bare domain are not.

**Why did a change not get reported?** Check `note` and `error`: the page may have been below the shrink safety threshold, below your minimum change percent, or blocked by robots.txt.

**Something missing?** Open an issue in the **Issues** tab. Only monitor pages you have the right to access.

### Related tools

- [Sitemap URL Extractor](https://apify.com/pavel.tashev/sitemap-url-extractor) — list every page of a website and track new or removed pages.
- [RSS Feed Reader & Finder](https://apify.com/pavel.tashev/rss-feed-reader) — read feed items in full, or find a website's feed.
- [SEO Metadata & On-Page Audit](https://apify.com/pavel.tashev/seo-metadata-audit) — audit the pages you are watching.
- [Broken Link Checker](https://apify.com/pavel.tashev/broken-link-checker) — find broken links and dead images on a whole site.

# Actor input Schema

## `targets` (type: `array`):

Pages, sitemap URLs, feed URLs or domains to watch. Each target is compared with what it looked like on the previous run of this Actor in your account.

## `watchMode` (type: `string`):

"Detect automatically" reads each target and treats it as a page, sitemap or feed. Choose a specific mode to force it; with "Sitemap" or "Feed" a bare domain is resolved through robots.txt and the usual locations.

## `firstRunOutput` (type: `string`):

What to do the first time a target is seen, when there is nothing to compare with. "Save baseline only" returns nothing and costs nothing. "Return a baseline row" returns one row per target so you can confirm what is being watched.

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

Optional CSS selector, for example main, #price, .product-info or .card .title. Only matching parts are compared, which removes noise from menus, banners and footers. Tags, #id, .class and space-separated paths are supported.

## `extract` (type: `array`):

Read named values from the page and report old → new with the difference. Each entry is {"name": "price", "selector": ".price", "type": "price"}. Type is text, number, price, date or boolean (in stock / sold out). Selectors support tags, #id, .class, \[attribute], \[attribute="value"], paths with spaces or >, and ::text or ::attr(content) at the end. A selector that matches nothing is reported with status "empty" and a reason, never as a silent null.

## `requireConsecutiveChanges` (type: `integer`):

1 reports a change as soon as it is seen. 2 or more hold it back until the same change is seen that many runs in a row, which filters out A/B tests and rotating banners. A real change is then reported one run later.

## `quietHours` (type: `string`):

Optional window when targets are skipped, for example 22:00-07:00. Skipped targets are not fetched and not charged. Use with a schedule that runs often.

## `checkOnlyOnDays` (type: `array`):

Optional weekdays when checks may run: mon, tue, wed, thu, fri, sat, sun. Other days are skipped without a request and without a charge.

## `minHoursBetweenChecks` (type: `integer`):

Skip a target that was checked less than this many hours ago. Lets one frequent schedule serve targets that need different rhythms. 0 turns the check off.

## `timezone` (type: `string`):

Time zone for quiet hours and weekdays: a name such as Europe/Sofia, or an offset such as +02:00. Defaults to UTC.

## `textOnly` (type: `boolean`):

On: compare the text a visitor sees, so markup and attribute changes are ignored. Off: compare the HTML as well, which catches changes in links, images and attributes.

## `ignoreVolatileText` (type: `boolean`):

On: strip times, ISO timestamps, relative dates and request tokens before comparing. These change on every visit and would otherwise report a change every run.

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

Replace every number before comparing. Useful for pages with view counters or stock levels when you only care about wording. Turn off when you watch prices.

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

Regular expressions removed before comparing, for example Copyright \d{4} or Cart (\d+). Use this for anything that changes on every visit.

## `minChangePercent` (type: `integer`):

Report a page only when at least this much of it changed. 0 reports any difference; 5 ignores small edits.

## `maxItemsPerTarget` (type: `integer`):

For sitemaps and feeds: stop after this many entries. When the limit is reached, removals are not reported, because the missing entries may simply not have been read.

## `maxDiffChars` (type: `integer`):

Cap for the added and removed text returned per target. Longer changes are cut and marked with diffTruncated.

## `shrinkSafetyPercent` (type: `integer`):

If a target returns less than this share of the previous content or entries, the run assumes a block page or a failed download: no change is reported and the saved snapshot is kept. Set 0 to turn the check off.

## `respectRobotsTxt` (type: `boolean`):

Skip targets that the site's robots.txt disallows. Turn this off only for sites you own.

## `concurrency` (type: `integer`):

Higher is faster across many sites. Requests to the same host are always spaced out.

## `userAgent` (type: `string`):

Optional custom User-Agent header. Leave empty to identify as WebsiteChangeWatcher.

## Actor input object example

```json
{
  "targets": [
    "https://www.python.org/downloads/",
    "https://blog.cloudflare.com/rss/"
  ],
  "watchMode": "auto",
  "firstRunOutput": "all",
  "extract": [
    {
      "name": "price",
      "selector": "meta[itemprop=\"price\"]::attr(content)",
      "type": "price"
    },
    {
      "name": "availability",
      "selector": ".stock",
      "type": "boolean"
    }
  ],
  "requireConsecutiveChanges": 1,
  "minHoursBetweenChecks": 0,
  "timezone": "UTC",
  "textOnly": true,
  "ignoreVolatileText": true,
  "ignoreNumbers": false,
  "minChangePercent": 0,
  "maxItemsPerTarget": 50000,
  "maxDiffChars": 5000,
  "shrinkSafetyPercent": 30,
  "respectRobotsTxt": true,
  "concurrency": 5
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `watchReport` (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 = {
    "targets": [
        "https://www.python.org/downloads/",
        "https://blog.cloudflare.com/rss/"
    ],
    "firstRunOutput": "all",
    "extract": [
        {
            "name": "price",
            "selector": "meta[itemprop=\"price\"]::attr(content)",
            "type": "price"
        },
        {
            "name": "availability",
            "selector": ".stock",
            "type": "boolean"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pavel.tashev/website-change-watcher").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 = {
    "targets": [
        "https://www.python.org/downloads/",
        "https://blog.cloudflare.com/rss/",
    ],
    "firstRunOutput": "all",
    "extract": [
        {
            "name": "price",
            "selector": "meta[itemprop=\"price\"]::attr(content)",
            "type": "price",
        },
        {
            "name": "availability",
            "selector": ".stock",
            "type": "boolean",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("pavel.tashev/website-change-watcher").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 '{
  "targets": [
    "https://www.python.org/downloads/",
    "https://blog.cloudflare.com/rss/"
  ],
  "firstRunOutput": "all",
  "extract": [
    {
      "name": "price",
      "selector": "meta[itemprop=\\"price\\"]::attr(content)",
      "type": "price"
    },
    {
      "name": "availability",
      "selector": ".stock",
      "type": "boolean"
    }
  ]
}' |
apify call pavel.tashev/website-change-watcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pavel.tashev/website-change-watcher"
        }
    }
}
```

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/yVB0HnNzEAwebIeJB/builds/sRyZvj1cGeeQMoLfy/openapi.json
