# Website Change Monitor: Page Change Detection & Diffs (`humble-echidna/website-change-monitor`) Actor

Watch web pages for changes, a website change tracker: give it page URLs (or a dataset of them) and each scheduled run returns only the pages that changed, with a line-by-line diff, the sections affected, and added and removed text. Unchanged pages cost less. Selector, ignore and keyword filters.

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

## Pricing

from $2.10 / 1,000 changed pages

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

### What does Website Change Monitor do?

**Website Change Monitor** watches **web pages for changes**. Give it the pages you care about; each run compares
every page with what it saw last time and returns **only the pages that changed**, with a **line-by-line diff**, the
**sections** the change is under, and the text that was **added and removed**. In short: **change detection** and a
**website change tracker** for the pages you choose.

Run it on a schedule and send the changes to Slack, email or a webhook: a competitor's pricing page, a supplier's
terms, a product's documentation, a tender or job page, a policy you have to track. It reads pages like a normal
visitor, without a browser, and it is **not** a screenshot or visual-diff tool: it compares the text.

[Input fields](https://apify.com/humble-echidna/website-change-monitor/input-schema) ·
[API](https://apify.com/humble-echidna/website-change-monitor/api) ·
[Use it from Claude, ChatGPT or Cursor (MCP)](#can-i-use-website-change-monitor-from-an-ai-agent-mcp)

**Jump to:** [Alerts](#get-page-changes-in-slack-email-or-a-webhook) ·
[Fields](#what-data-does-website-change-monitor-return) · [Price](#how-much-does-it-cost-to-monitor-a-website-for-changes) ·
[How to use](#how-to-monitor-a-web-page-for-changes) · [Input](#input) · [Output](#output) ·
[AI agents (MCP)](#can-i-use-website-change-monitor-from-an-ai-agent-mcp) · [Limits](#limits) · [FAQ](#faq)

**Try it in one click:** the input comes pre-filled with Wikipedia's Main Page, which changes every day. The first
run records it (1 page, $0.003, plus $0.00005 for the run start); run it again tomorrow to see what changed.
**Then replace it with the pages you actually want to watch.**

### Get page changes in Slack, email or a webhook

1. Put your pages in **Pages to watch** and click **Start**. This first run returns every page as `new`: it's the
   baseline the next runs compare with.
2. Click **Save as a new task** (top right of the actor page). A task keeps its own memory, so it always compares
   with its own last run.
3. In Apify Console, open **Schedules**, click **Create new**, set how often (for example every day at 07:00, or
   every hour), then **Add** your task.
4. On the task, open the **Integrations** tab and pick where the changes go:
   - **Slack**: click **Configure**, sign in, pick the channel and the "run succeeded" event. A useful message:
     `{{resource.statusMessage}}` and a link to the changes,
     `<https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}|page changes>`. Each row's `summary`
     reads like "1 line added, 1 removed in “Plans”".
   - **Gmail**: click **Connect with Google**, set the subject and body, and attach the dataset (for example as CSV).
   - **HTTP webhook**: event `ACTOR.RUN.SUCCEEDED`, your URL. Apify POSTs `{"eventType": ..., "resource": {...}}`;
     `GET https://api.apify.com/v2/datasets/<resource.defaultDatasetId>/items?format=json` (with your API token)
     returns the changed pages.

Apify's integrations fire after every successful run, including quiet ones: a quiet run's dataset is empty, and its
status message says how many pages were unchanged.

**A page that fails is never reported as a change.** When a page is down, blocked by robots.txt or a bot check, or
needs JavaScript, it isn't compared: it's listed as failed in the log and `RUN_STATS`, isn't charged, and the next run
compares with the last text it really had. A page that answers 404 or 410 after being seen before is reported once as
`removed`, and as `changed` if it comes back.

### What data does Website Change Monitor return?

One row per page that is new, changed or removed since the last run:

| Field | Example | Notes |
|---|---|---|
| `url` | `https://example.com/pricing` | The page as you listed it |
| `status` | `changed` | `new` (first check), `changed`, or `removed` (404/410) |
| `summary` | `1 line added, 1 removed in “Plans”` | One line for an alert |
| `changedSections` | `["Plans"]` | The headings the changes are under |
| `added` / `removed` | `["Starter costs $12 a month."]` | The changed lines (first 100) |
| `linesAdded` / `linesRemoved` | `1` / `1` | Complete counts; `null` for a new page |
| `diff` | `-Starter costs $10…` | Unified diff, 2 lines of context; `null` for a new page |
| `matchedKeywords` | `["price"]` | With **Only report changes that mention**; `null` without |
| `text` | `## Plans\nStarter costs…` | The watched text now (first 20,000 characters) |
| `title`, `finalUrl` | | The page's title, and its address after redirects |
| `checkedAt`, `previousCheckedAt`, `previousChangeAt` | `2026-10-02T04:44:00Z` | When it was checked, last checked, and last changed |

The full list is under [Output](#output).

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

You pay per page checked, and a page that didn't change costs half as much as one that did:

- **$3.00 per 1,000 changed pages**: a row in your results, for a page that is new (its first check), changed or
  removed;
- **$1.50 per 1,000 unchanged pages**: a page checked, found unchanged and not returned;
- plus $0.00005 each time a run starts.

The changed-page price is lower on paid Apify plans: $2.70 (Starter), $2.40 (Scale), $2.10 (Business) per 1,000.

Pages that fail (down, blocked, not found on the first check, JavaScript-only, a selector that matches nothing) are
never charged.

- **The pre-filled example:** the first run is 1 new page, $0.003; a later run is 1 changed page, $0.003.
- **A month, for example:** 50 competitor pages checked daily, 3 of them changing on a typical day: 30 × (47 ×
  $0.0015 + 3 × $0.003) + the start fees ≈ **$2.39**. The same 50 pages every hour, with the same 3 changes a day: ≈
  **$54.17**.
- **Caps:** **Max pages per run** in the input, and **Maximum cost per run** in the run options. The run stops cleanly
  at whichever comes first; pages not checked are checked by the next run.

### How to monitor a web page for changes

1. Open Website Change Monitor and click **Try for free** (or **Start** if you're signed in).
2. Put the pages in **Pages to watch**, one per line.
3. Optional: pick the **Part of the page** to watch, a **CSS selector**, text to **ignore**, or words to **watch for**.
4. Click **Start**. The first run records every page; open the **Output** tab to see them.
5. Save it as a task and schedule it ([Alerts](#get-page-changes-in-slack-email-or-a-webhook)); each later run
   returns only what changed.

### Example: a busy page, and how to quiet it

Two pages, checked twice, 2 minutes apart (real output from local runs on 2026-10-02):

```json
{"urls": ["https://en.wikipedia.org/wiki/Main_Page", "https://news.ycombinator.com/"],
 "monitorName": "readme-example"}
```

The first run returned both pages as `new` (2 × $0.003 = $0.006). The second said "2 of 2 pages checked (compared
with the run of 2026-10-02 04:54 UTC): 1 changed, 0 new, 0 removed, 1 unchanged" and returned one row, for $0.0045
($0.003 for the changed page, $0.0015 for the unchanged one). Shortened (usernames replaced by `…`, 2 of 8 lines
shown):

```json
{
  "id": "0f63a2a5a5620b745938e6a2",
  "url": "https://news.ycombinator.com/",
  "title": "Hacker News",
  "status": "changed",
  "summary": "8 lines added, 8 removed in “Hacker News”",
  "changedSections": ["Hacker News"],
  "linesAdded": 8,
  "linesRemoved": 8,
  "added": ["| | 42 points by … 1 hour ago \\| hide \\| 14 comments | |",
            "| | 44 points by … 2 hours ago \\| hide \\| 14 comments | |", "..."],
  "removed": ["| | 41 points by … 1 hour ago \\| hide \\| 14 comments | |",
              "| | 43 points by … 2 hours ago \\| hide \\| 13 comments | |", "..."],
  "diff": "--- before\n+++ after\n@@ -4,9 +4,9 @@\n ...\n-| | 41 points by … 1 hour ago ...\n+| | 42 points by … 1 hour ago ...",
  "wordCount": 891,
  "checkedAt": "2026-10-02T04:55:53Z",
  "previousCheckedAt": "2026-10-02T04:54:08Z",
  "previousChangeAt": null
}
```

Every changed line was a vote or comment count, which is noise for most people watching a page like this. Adding
**Ignore text matching** `\d+ points?`, `\d+ comments?` and `\d+ (minute|hour|day)s? ago` turns those into `…`;
the next run compares afresh (not reported), and after that only stories that appear, disappear or move are
changes. Wikipedia's Main Page didn't change in those 2 minutes; it changes daily.

### Input

| Field | What it does |
|---|---|
| **Pages to watch** (`urls`) | The pages, one per line; `https://` is added when missing. Up to 2,000 per run. |
| **Or: page URLs from a dataset** (`datasetId`) | Another actor's results, e.g. every page [Sitemap URL Extractor](https://apify.com/humble-echidna/sitemap-urls) found on a site. `urls` is then ignored. |
| **Part of the page** (`contentScope`) | `main` (default): the main content, without menus, headers, footers and sidebars. `page`: all visible text. |
| **Only this part of the page** (`cssSelector`) | A CSS selector such as `#pricing` or `.product-price`; only matching elements are compared. |
| **Ignore text matching** (`ignorePatterns`) | Regular expressions for text that changes without meaning anything (view counts, "updated 5 minutes ago"); each match becomes `…` before comparing. |
| **Only report changes that mention** (`watchFor`) | Words or phrases; a change is returned only when an added or removed line contains one. |
| **Count changed link addresses as changes** (`includeLinks`) | Off by default: link text is compared, not its address (tracking parameters change on every visit). |
| **Monitor name** (`monitorName`) | Which memory to compare with. Tasks get their own automatically; set it when you run several monitors without tasks. |
| **Max pages per run** (`maxResults`) | Caps the pages checked in one run. |

```json
{
  "urls": ["https://example.com/pricing", "https://example.com/terms"],
  "contentScope": "main",
  "ignorePatterns": ["\\d+ (views|comments)"],
  "watchFor": ["price", "$"]
}
```

### Output

Every row has the same fields; a field that doesn't apply is `null` (for example `diff` on a new page, `text` on a
removed one).

| Field | Type | Meaning |
|---|---|---|
| `id` | string | Stable id of the page (from its URL), the same on every run |
| `url` | string | The page as you listed it |
| `finalUrl` | string or null | Its address after redirects |
| `title` | string or null | The page's title |
| `status` | string | `new`, `changed` or `removed` |
| `summary` | string | What changed and where, in one line |
| `changedSections` | array | The headings the changes are under; `(top of page)` before the first heading |
| `linesAdded`, `linesRemoved` | integer or null | Complete counts of changed lines |
| `added`, `removed` | array | The changed lines: the first 100, each up to 1,000 characters |
| `diff` | string or null | Unified diff with 2 lines of context, up to 30,000 characters |
| `diffTruncated` | boolean | The diff was cut (the counts stay complete) |
| `matchedKeywords` | array or null | The watched words found in the changed lines |
| `text` | string or null | The watched text now, as Markdown lines, up to 20,000 characters |
| `textTruncated` | boolean | `text` was cut at 20,000 characters |
| `textCutAtLimit` | boolean | The page has over 200,000 characters of text; only the first 200,000 are compared |
| `wordCount` | integer or null | Words in the watched text |
| `contentHash`, `previousContentHash` | string or null | Hashes of the watched text now and last time |
| `checkedAt` | string | When this run checked the page (ISO 8601, UTC) |
| `previousCheckedAt`, `previousChangeAt` | string or null | When this monitor last checked it, and last saw it change |
| `sourceTitle`, `sourcePlaceId`, `sourceIndex` | | With a dataset: the source item's title, placeId and position |

The run's `RUN_STATS` record lists every page with its status (`new`, `changed`, `removed`, `unchanged`) or the reason
it failed, and the totals (`pagesChanged`, `pagesUnchanged`, ...).

### Run it on a schedule, or from your own code

1. Save your input as a [task](https://docs.apify.com/platform/actors/running/tasks) and add it to a
   [schedule](https://docs.apify.com/platform/schedules); the steps are under
   [Alerts](#get-page-changes-in-slack-email-or-a-webhook).
2. Collect results: download the dataset as JSON, CSV or Excel; fetch the latest run's changes from the
   [API](https://apify.com/humble-echidna/website-change-monitor/api)
   (`GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=json`, with
   your API token); let a [webhook](https://docs.apify.com/platform/integrations/webhooks) tell your system when a
   run succeeds; or connect it to Make, Zapier or n8n through
   [Apify's integrations](https://docs.apify.com/platform/integrations).

#### Can I use Website Change Monitor from an AI agent (MCP)?

Yes, through [Apify's MCP server](https://docs.apify.com/platform/integrations/mcp), from Claude, ChatGPT, Cursor or
any other MCP client. Add this to your client's MCP configuration; your client signs you in to Apify:

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

To use an [Apify API token](https://console.apify.com/settings/integrations) instead of signing in, add
`"headers": {"Authorization": "Bearer <APIFY_TOKEN>"}` next to `url`.

The agent passes the pages and a monitor name of its own, e.g.
`{"urls": ["https://example.com/pricing"], "monitorName": "agent-pricing"}`, and reads `summary` and `added` /
`removed` for each changed page. An empty result means nothing changed since its last call with that name.

### Who it's for

Product, marketing and sales teams watching competitors' pricing, plans and feature pages; procurement and legal
teams tracking suppliers' terms, policies and price lists; anyone waiting for a page to change (a tender, a job, a
"coming soon" or out-of-stock page). The recurring job: the same list of pages, checked every day or every hour, with
an alert only when something actually changed.

### Why this one?

- **Unchanged pages cost half.** A page that didn't change is charged $1.50 per 1,000, a changed one $3.00, so
  checking often costs less than a flat per-check price. No compute, proxy or storage charges on top, apart from the $0.00005 run-start fee.
- **Changes you can act on.** Each change says which section it's in and which lines were added and removed, plus a
  unified diff, not just "the page changed".
- **Less noise.** Main content only by default (a new menu item or footer line isn't a change), link addresses
  ignored by default, your own ignore patterns, and an optional keyword filter.
- **Failures aren't changes.** A page that's down, blocked or replaced by a bot check isn't compared or charged; the
  next run compares with its last real text.
- **Polite and safe.** It identifies itself honestly (User-Agent `HumbleEchidnaApify`), follows each site's
  robots.txt and Crawl-delay, makes at most 2 requests per site at once, and only requests public web addresses on
  the standard ports (80 and 443).
- **Reliable.** One failing page never affects the others. The log and `RUN_STATS` say which page had a problem and
  why.

### Limits

- No browser: pages that build their text with JavaScript are reported as failed (not charged). Most public pages
  (pricing, docs, terms, news, listings) are served as HTML and work.
- Text only: a changed image, color or layout isn't a change unless its text (or, with `includeLinks`, a link
  address) changes.
- Up to 2,000 pages per run and 200,000 characters of text per page; split bigger sites into several tasks.
- No logins, no proxies: pages behind a login, and sites that refuse automated visitors or answer with a bot check,
  are reported and not charged.

### FAQ

#### How does Website Change Monitor know what changed since the last run?

It keeps each monitor's memory in a key-value store named `website-change-monitor-memory` in your own Apify account:
per page, a hash of its watched text, and the text itself when it changed. Each run compares with that and moves the
memory on only for the pages you got a result for, so a run stopped by your maximum cost reports the change next time.

#### Why is a page I didn't change reported as changed?

Usually a counter, date or rotating block in the page's main content. Add an **Ignore text matching** pattern for it
(for example `\d+ views` or `updated .* ago`), watch only the part you care about with a **CSS selector**, or use
**Only report changes that mention**. Changing these settings compares the affected pages afresh on the next run
(not reported, charged as unchanged), so you don't get a burst of false changes.

#### Can I watch a whole website?

Yes: run [Sitemap URL Extractor](https://apify.com/humble-echidna/sitemap-urls) on the site and pick its dataset in
**Or: page URLs from a dataset**, up to 2,000 pages per run. A page that newly appears in the list is reported as
`new`.

#### Why did a page come back as "blocked by robots.txt" or "needs JavaScript"?

The site's robots.txt disallows automated visitors on that page, or the page has no text until a browser runs its
scripts. Both are listed in the log and `RUN_STATS`, and neither is charged.

#### Does it run on Apify's schedule, or does it watch continuously?

It checks when it runs. Schedule it as often as you need (hourly is common); each run is quick for a few dozen pages.

#### Is it legal to monitor a website for changes?

It reads public pages anyone can open, without logging in, identifies itself honestly and follows each site's
robots.txt. What you do with the text is up to you and the site's terms; don't use it to republish someone else's
content.

### Related actors

| Actor | Use it when |
|---|---|
| [Sitemap URL Extractor](https://apify.com/humble-echidna/sitemap-urls) | You want to watch every page of a site: it lists the URLs to feed in. |
| [Website & Page to Markdown for RAG and LLMs](https://apify.com/humble-echidna/page-to-markdown) | You want the full pages as clean Markdown, for example to refresh a knowledge base. |
| [Product Price & Stock Monitor](https://apify.com/humble-echidna/product-offers) | The pages are product pages and you want price and stock as fields. |

### Feedback and support

Found a bug, or a page that's reported as changed when it isn't? Open an issue on the **Issues** tab with the input you
used.

### Versions

Current version: **0.1**. See the Changelog tab for what changed in each version.

# Changelog

This Actor's version history is a separate document: https://apify.com/humble-echidna/website-change-monitor/changelog.md

# Actor input Schema

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

The pages to check for changes, one per line, e.g. https://example.com/pricing; a missing https:// is added. Each run compares every page with what this monitor saw last time and returns only the pages that are new, changed or removed. Pages the site's robots.txt disallows are skipped and listed in the log. Up to 2,000 pages per run. Ignored when datasetId is set.

## `datasetId` (type: `string`):

Default empty. One of your Apify datasets, picked here or given by id, e.g. the pages Sitemap URL Extractor found on a site; to chain this actor after another in an integration, `{{resource.defaultDatasetId}}`. Each item's page URL is used as a line of urls (urls is ignored). Duplicates are checked once. Each row carries sourceTitle, sourcePlaceId and sourceIndex from its item. Reads at most 20,000 items, read-only.

## `datasetUrlField` (type: `string`):

Only with datasetId: the item field that holds the page URL, e.g. `url`, or a dotted path such as `metadata.url`. Leave empty (the default) to find it automatically: the first of url, pageUrl, link, website and loadedUrl that has a web address in the first 100 items, then the same names one level down.

## `contentScope` (type: `string`):

Default `main`: the page's main content, found the way reader views find it, so a new menu item, cookie banner or footer link isn't reported as a change. `page`: all visible text, menus and footers included. Ignored when cssSelector is set.

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

Default empty. Watch only the elements matching this CSS selector, e.g. `#pricing`, `.product-price` or `main table`; several matches are watched together, in page order. A page where it matches nothing is reported as failed (not charged).

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

Default empty. Regular expressions (case-insensitive) for text that changes without meaning anything, e.g. `\d+ (views|comments)`, `updated \d+ minutes ago` or `\d{1,2}:\d{2}`. Each match is replaced by … before pages are compared, so a page whose only change is in that text is unchanged.

## `watchFor` (type: `array`):

Default empty (every change is reported). Words or phrases, case-insensitive: a changed page is returned only when one of them is in an added or removed line, e.g. `price`, `out of stock`, `deadline`. Other changes are remembered and charged as an unchanged check, not returned.

## `includeLinks` (type: `boolean`):

Default false: a link's text is compared, not its address, because tracking parameters and session ids in links change on every visit. Turn it on to also report a link that now points somewhere else.

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

Default empty. Which memory this run compares with. A saved task gets its own memory automatically; runs started without a task (API calls, the Console form) share one called `default`. Give each monitor its own name when you run several without tasks, so they don't compare with each other's last run. The memory is kept in a key-value store named website-change-monitor-memory in your account.

## `maxResults` (type: `integer`):

Stop after checking this many pages, e.g. 50. Minimum 1; leave empty (the default) for no limit. The run also stops cleanly at the maximum cost per run you set in the run options, whichever comes first; pages not checked are checked next run.

## Actor input object example

```json
{
  "urls": [
    "https://en.wikipedia.org/wiki/Main_Page"
  ],
  "contentScope": "main",
  "includeLinks": false
}
```

# Actor output Schema

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

No description

## `runStats` (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://en.wikipedia.org/wiki/Main_Page"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("humble-echidna/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://en.wikipedia.org/wiki/Main_Page"] }

# Run the Actor and wait for it to finish
run = client.actor("humble-echidna/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://en.wikipedia.org/wiki/Main_Page"
  ]
}' |
apify call humble-echidna/website-change-monitor --silent --output-dataset

```

## MCP server setup

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