# Website Screenshots: Full Page, Mobile, PDF & Change Alerts (`rookdev/website-screenshot-monitor`) Actor

Screenshot any list of URLs (full page or viewport, desktop or mobile, PNG, JPEG or PDF, one element, no cookie banners), or schedule it to compare each page with the last run and get how much it changed, with the changed area and a Slack-ready summary.

- **URL**: https://apify.com/rookdev/website-screenshot-monitor.md
- **Developed by:** [Rook Dev](https://apify.com/rookdev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 screenshots

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

## Website Screenshots: Full Page, Mobile, PDF & Change Alerts

**Get a clean screenshot of every page on your list (full page or window, desktop or mobile, PNG, JPEG or PDF, or just one element), or schedule it to see which pages changed since the last run, by how much and where, with a one-line alert ready for Slack.**

- **Screenshots without the clutter.** Cookie banners (OneTrust, Cookiebot, Didomi, Usercentrics and about 20 more consent tools, plus unknown ones found by their wording) and chat bubbles are hidden, never clicked. Lazy images are scrolled into view first. Full page, window only, a custom width and height, or one element by CSS selector.
- **Visual change monitoring built in.** Turn on *Compare with the previous run*, schedule it, and each page gets `changePercent`, the `changedArea` box, a link to the previous screenshot and an image with the changes in red. Alerts read "Pricing page on example.com changed 12%".
- **You never pay for a broken result.** Pages that time out, return 404 or 500, ask for a login or show a bot check get a free row saying why. One bad URL never fails the run.
- **Fast and bounded.** Up to four pages in parallel in one browser, at most 60 seconds per page (waits and delay included), at most 5,000 URLs per run, and a size cap for endless or very wide pages (3,840 px wide, 16 megapixels per image), so a run never hangs or runs out of memory.

Each screenshot is saved in the run's storage and linked from the dataset, so you can open it, download it or pass the link on.

### Sample output

A monitoring row from a test run on a demo pricing page whose price box changed colour between two runs:

```json
{
  "status": "changed",
  "summary": "Pricing page on example.com changed 0.7%",
  "pageId": "example.com/changing",
  "label": "Pricing page",
  "url": "http://example.com/changing",
  "finalUrl": "http://example.com/changing",
  "title": "Pricing",
  "httpStatus": 200,
  "device": "desktop",
  "fullPage": true,
  "selector": null,
  "format": "png",
  "width": 1280,
  "height": 1200,
  "pageHeight": 1200,
  "truncated": false,
  "screenshotKey": "screenshot-example.com_changing-4c1c059736.png",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/Ub4cNs6vXm1Jo8LfG/records/screenshot-example.com_changing-4c1c059736.png",
  "pdfUrl": null,
  "changePercent": 0.65,
  "changedPixels": 10000,
  "changedArea": { "x": 100, "y": 300, "width": 200, "height": 50 },
  "previousScreenshotUrl": "https://api.apify.com/v2/key-value-stores/Hq3rXy8bM2kTzWv1L/records/base-example.com_changing-b3cfbb10df64-ec1ee42ba8.png",
  "previousCapturedAt": "2026-10-28T07:00:00Z",
  "diffImageUrl": "https://api.apify.com/v2/key-value-stores/Ub4cNs6vXm1Jo8LfG/records/diff-example.com_changing-16fc7c1016.png",
  "capturedAt": "2026-10-29T07:00:00Z",
  "warnings": [],
  "firstSeenAt": "2026-10-28T07:00:00Z"
}
```

A normal (non-monitoring) run returns the same fields with `status: "captured"` and the change fields empty. The **Screenshots** view of the Output tab shows each image as a thumbnail.

### Who uses it

- **Marketing and product teams:** watch competitors' pricing, plans and landing pages and get told the day they change, with the changed area outlined.
- **Agencies and SEO teams:** dated full-page captures of client and competitor sites, desktop and mobile, for reports and before/after comparisons.
- **Developers and QA:** a visual check of production pages after each deploy; a changed percentage above your threshold is your regression alarm.
- **Compliance and legal teams:** a dated visual record of terms, disclosures and price pages, and an alert when they change.
- **AI agents:** a narrow, predictable tool for "show me this page" or "did this page change?"; works through the Apify MCP server.

### How to use it

1. Paste pages into **URLs to capture**, one per line. Add a name for alerts with `Name | URL`, for example `Competitor pricing | https://example.com/pricing`.
2. Pick the **Device** (desktop, mobile or tablet), **Full page** or window only, and the **Image format**. Optionally capture one element with a CSS selector, or wait for an element before capturing.
3. Click **Start**. Open the screenshots from the Output tab, or read the rows (with links) as JSON, CSV or Excel or through the API.

**To monitor:** turn on **Compare with the previous run**, click **Save as a new task** and add a schedule (daily or hourly). The first run saves each page as its baseline (or, with **Start quietly**, saves it without returning rows); from then on every page is compared with its last reported screenshot. Turn on **Only return pages that changed** if you want rows for changes only.

#### Input examples

Screenshots of three pages on a phone, as JPEG, plus PDFs:

```json
{
  "urls": ["https://apify.com", "https://apify.com/pricing", "https://docs.apify.com"],
  "device": "mobile",
  "format": "jpeg",
  "savePdf": true
}
```

Daily monitor of competitor pricing tables, changes only, ignoring a rotating banner:

```json
{
  "urls": ["Acme pricing | https://acme.example/pricing", "Globex plans | https://globex.example/plans"],
  "selector": "#pricing",
  "hideSelectors": [".promo-carousel"],
  "compareWithPreviousRun": true,
  "onlyChangedPages": true,
  "skipExistingOnFirstRun": true
}
```

#### Main inputs

| Input | What it does | Default |
| --- | --- | --- |
| `urls` | Pages, one per line; `Name \| URL` adds a name for alerts | required |
| `fullPage` | Whole page, or only the window | on |
| `device` | `desktop` 1280 x 800, `mobile` 412 x 915 at 2x, `tablet` 800 x 1280 at 2x | desktop |
| `viewportWidth`, `viewportHeight` | Custom window size, e.g. 1440 x 900 | device preset |
| `format`, `jpegQuality`, `savePdf` | PNG or JPEG; also an A4 PDF | PNG |
| `selector` / `waitForSelector` | Capture one element / wait for one before capturing | none |
| `hideCookieBanners`, `hideSelectors` | Hide banners and chat widgets; hide your own elements | on / none |
| `darkMode`, `delayMs` | Dark theme; extra wait up to 10 s (inside the page time limit) | off / 0 |
| `maxPageHeight`, `pageTimeoutSecs` | Height cap (10,000 px); time limit per page, waits and delay included (45 s, at most 60) | |
| `compareWithPreviousRun`, `minChangePercent` | Monitoring; smallest change to report | off / 0.1% of the page |

### Output

One row per page. `status` is `captured` in a normal run, or `new`, `changed` or `unchanged` when monitoring. Every row has a one-line `summary`, the `screenshotUrl`, the image size, the page's final URL, title and HTTP status, and `warnings` (for example when a page was cut at the height cap: `truncated: true`). Monitoring rows add `changePercent`, `changedPixels`, `changedArea` (`x`, `y`, `width`, `height` in screenshot pixels), `previousScreenshotUrl`, `previousCapturedAt`, `diffImageUrl` and `firstSeenAt`.

Pages that produced no screenshot get a **free** row with `status` `not_found`, `blocked` (access denied or a bot check), `login_required`, `element_not_found`, `invalid_input`, `error` or `skipped` (not tried: your spending limit was reached, the run stopped early, or the list was over 5,000 URLs; one row covers all of them), and a plain-English `error`. When monitoring, a page that suddenly renders blank (one colour) gets a free `blank_page` row and keeps its baseline; only if it is still blank on the next run is it reported as changed, so a passing glitch never sends a false alert. A `RUN_SUMMARY` record in the run's key-value store lists every page with its result.

### Get alerts in Slack, Google Drive, Make, Zapier or n8n

1. Turn on monitoring, click **Save as a new task**, and add a schedule to the task.
2. On the task's **Integrations** tab, pick **Slack**, connect your workspace, choose the **Run succeeded** event and a channel, and paste this as the message:

```handlebars
*{{resource.statusMessage}}*
<https://console.apify.com/storage/datasets/{{resource.defaultDatasetId}}|Open the screenshots>
```

The status message reads like *Checked 20 page(s): 2 changed, 18 unchanged, 0 new. Acme pricing on acme.example changed 12%; Globex plans on globex.example changed 0.4%.*, so the alert itself says what changed.

- **Google Drive:** the **Upload results to GDrive** integration saves each run's rows (with the image links) as CSV or JSON.
- **Make, Zapier, n8n:** use the Apify app's "Actor run finished" trigger, filter rows on `status = changed`, and post the `summary` with the `diffImageUrl`.
- **Webhooks:** send the run to your own endpoint and read the dataset through the API.

### Pricing

Pay per event. No platform usage charges on top: the price is the price.

| Event | Price |
| --- | --- |
| Screenshot delivered (a row with a screenshot; PDF included) | $3.00 per 1,000 ($0.003 each) |
| Page compared with its previous screenshot (monitoring only) | $2.00 per 1,000 ($0.002 each) |
| Run start | $0.0001 per GB of run memory: $0.0002 per run at the default (and maximum) 2 GB, $0.0001 at 1 GB |

Pages that fail, need a login, are blocked, are not found, render blank or are skipped are free, and so are pages a run did not get to when it stopped early. A comparison is charged only after the page's row was delivered.

- **One-off batch:** 500 product pages, full page on desktop: 500 x $0.003 + $0.0002 = **$1.50**.
- **Daily monitoring:** 20 competitor pages every day with *Only return pages that changed*, about 2 changes a day: 20 x $0.002 + 2 x $0.003 + $0.0002 = $0.0462 a day, **about $1.39 a month**. With a row for every page every day: about $3.01 a month.
- **One screenshot through the API:** $0.003 + $0.0002 = **$0.0032**.

Set a maximum cost per run in the run options and the Actor stops cleanly when it is reached: pages it could not try get a free `skipped` row, and a change it could not deliver is reported on the next run. Nothing is charged twice.

### FAQ

**Is this allowed?** It opens only the public pages you list, once per run, the way a person opens them in a browser: it does not crawl, follow links, log in, fill in forms, click consent buttons or try to get past bot checks. Because it never crawls, it does not apply robots.txt crawl rules, the same as Apify's own Website Screenshot Generator. Follow the terms of the sites you capture. The output holds only what you asked for: page URLs, titles and links to images of your target pages; it extracts no text or personal data from them, but a screenshot shows whatever the page shows, so do not point it at pages with personal data you have no reason to keep. A screenshot is a copy of the page: the content stays the site's copyright, so capture pages you may lawfully record (your own pages, or competitors' public pages for internal reference) and do not republish the images.

**Why did a page get a `blocked` or `login_required` row?** The site refused access (HTTP 403), showed a bot check, redirected to a login page or asked for a password, or the page (or a frame in it) led to a private network address. Pages behind a login are never captured, and you are not charged. This version uses no proxies, so sites that block cloud browsers stay blocked.

**Why was a run stopped early, and what does it cost?** Three safety limits stop a run before it has opened every page: your own maximum cost per run; 50 pages in a row that could not be captured (the list or the sites are probably down); and a limit on browser time that produced no paid results (about 7 minutes at 2 GB, more once pages are delivered, so normal runs never reach it). The pages it did not get to are not opened and cost nothing; they share one free `skipped` row, the status message says why, and `RUN_SUMMARY` lists them. Run them again later, or split a large list across tasks. A page that makes more than 1,500 requests (it never stops loading) is stopped and gets a free `error` row. If one Apify account's runs keep producing nothing to charge, new runs from that account stop early for up to 24 hours, with a free row that says so; normal use never gets near this.

**Can it reach private or internal addresses?** It is built not to, and never gives you what it finds there. URLs with private, local or reserved addresses are refused in any spelling (`10.0.0.1`, `127.1`, `0x7f.0.0.1`, `169.254.169.254` and so on). Before the browser sends any request, its host is looked up and the request is refused if the address is private or cannot be looked up. Afterwards the real server address of every response is checked, as are redirects and frames, so a site whose name only seems public (DNS rebinding) or that redirects inwards gets a free `blocked` row instead of a screenshot. WebSockets and popups are never opened. One limit remains: in the rebinding case the browser has already sent its requests to that address before the capture is refused.

**Why does a page show as changed on every run?** Something on it moves: a carousel, an ad, a "posted 5 minutes ago" line, a live counter. Open `diffImageUrl` to see what, then hide it with **Hide these elements** (the best remedy for carousels, dates and ads), capture only the part you care about with **Capture one element**, or raise **Smallest change to report**. CSS animations and the text cursor are frozen for every capture, colour differences of up to 24 of 255 (font smoothing, image re-encoding) do not count, and changes under 10 pixels or under 0.1% of the page (the default) are not reported.

**How is the change measured?** The two screenshots are compared pixel by pixel in the browser (a pixel counts when any colour channel moved by more than 24 of 255). `changePercent` is the share of changed pixels over the larger of the two images, so a page that grew taller counts its new part as changed. By default a page counts as changed from 0.1% of its pixels, about a changed paragraph or banner on a full page; a changed price is smaller, so capture the price element (then the share is of that element) or choose **Any visible change**. The baseline moves only when a change is reported, so small changes that each stay below your threshold add up until they are reported.

**What happens with very long or very wide pages?** Full-page captures stop at **Max page height** (10,000 px by default, up to 16,000) and the row says `truncated: true`. To fit in memory, every image also stays under 16,000 pixels tall and 16 megapixels in all, and pages are captured at most 3,840 px wide: mobile stops at 8,000 px, tablet at 5,000 px and a 3,840 px wide window at about 4,100 px. Pages taller than the cap are captured in a cheaper way, by enlarging the window to the captured height (about 1 second instead of 15 to 30 for a very long page); sections sized to the window height, such as a 100vh hero, are pinned to their normal size first, but other window-relative layouts can look slightly different from a normal full-page capture. Infinite-scroll pages stop at the cap too, and lazy-image scrolling stops after 6 seconds. A PNG over 15 MB (a page full of photos) is saved as a JPEG, and the row says so.

**Does it accept cookie banners for me?** No. Banners and their dark overlays are hidden, nothing is clicked, so no consent is given on your behalf. If a site only shows its content after consent, turn **Hide cookie banners** off to see the page as visitors first see it.

**How long are screenshots kept?** In the run's storage for your plan's data retention period. When monitoring, each page's last two screenshots stay in the monitoring memory (a named key-value store), so the `previousScreenshotUrl` of the latest change keeps working; older ones are deleted, so a `previousScreenshotUrl` in an older row stops working after two more changes of that page (download what you want to keep). Pages that no monitoring run has checked for 30 days are forgotten (their record and images are deleted), so a monitor scheduled at least every 30 days keeps its memory; the rest of the store stays until you delete it, and deleting it in Storage starts every monitor afresh.

**Why is the first monitoring run different?** There is nothing to compare with yet, so each page is saved as its baseline (`new`). With **Start quietly**, the baselines are saved without rows and each is charged as one comparison instead of a screenshot. Each saved task, and each set of capture options (device, size, element, hidden elements, threshold), keeps its own memory, and normal runs never touch it.

**Switching from apify/screenshot-url?** Put your URLs one per line. Through the API, its field names `delay`, `scrollToBottom` and `selectorsToHide` are understood too, and its `format: "pdf"` gives a PNG screenshot plus the PDF (the same as **Also save a PDF**).

**Can I use it from code or an AI agent?** Yes: through the Apify API, the Python and JavaScript clients, or the Apify MCP server. Inputs and outputs are stable and documented above.

### Support

Open an issue on the Issues tab with the URL and your input; I aim to answer within a day.

# Changelog

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

# Actor input Schema

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

One page per line, for example https://apify.com/pricing. Add a name for alerts with "Name | URL", for example "Competitor pricing | https://example.com/pricing". Only public pages: pages that ask for a password are never captured. At most 5,000 per run; split longer lists across runs or tasks.

## `fullPage` (type: `boolean`):

Capture the whole page from top to bottom (up to the max page height). Turn off to capture only what fits in the window (the viewport).

## `device` (type: `string`):

Desktop is a 1280 x 800 window. Mobile is a 412 x 915 phone screen and tablet an 800 x 1280 one, both at 2x pixel density with touch, so sites show their mobile layout.

## `viewportWidth` (type: `integer`):

Optional. Overrides the device's window width, for example 1440 or 1920. Leave empty to use the device preset.

## `viewportHeight` (type: `integer`):

Optional. Overrides the device's window height. With Full page off, this is the height of the screenshot, for example 600 for just the top of the page.

## `format` (type: `string`):

PNG is pixel-exact (best for monitoring and text). JPEG files are several times smaller (good for archives and thumbnails). For a PDF, turn on "Also save a PDF".

## `jpegQuality` (type: `integer`):

JPEG only: 1 (smallest file) to 100 (best quality).

## `savePdf` (type: `boolean`):

Also save each page as an A4 PDF with backgrounds, linked in pdfUrl. Included in the screenshot price.

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

Optional. Capture only the first element matching this CSS selector instead of the page, for example #pricing or .plans. When monitoring, changes are measured on that element only, which makes small changes (a price) show up clearly.

## `hideCookieBanners` (type: `boolean`):

Hide consent banners (OneTrust, Cookiebot, Didomi, Usercentrics and about 20 more consent tools, plus unknown banners found by their wording) and chat launchers (Intercom, Drift, HubSpot, Zendesk and more). Nothing is clicked or accepted: they are only hidden.

## `hideSelectors` (type: `array`):

CSS selectors of elements to hide, one per line, for example .ad-slot or #rotating-banner. Their space is kept, so the layout does not move. Use it to keep rotating content out of change monitoring.

## `waitForSelector` (type: `string`):

Optional. Wait until this element is visible before capturing, for example .price-table. If it never appears within the page timeout, you get a free row explaining that and no charge.

## `delayMs` (type: `integer`):

Wait this long after the page has loaded (and lazy content was scrolled in) before capturing, for animations or late widgets. 0 for none, at most 10 seconds; it counts inside the page timeout.

## `scrollToLoadLazyContent` (type: `boolean`):

Scroll down the page (for at most 6 seconds, up to the max page height) and back up before a full-page capture, so lazy-loaded images and scroll animations appear.

## `darkMode` (type: `boolean`):

Ask the page for its dark theme (prefers-color-scheme: dark). Pages without a dark theme look the same.

## `maxPageHeight` (type: `integer`):

Full-page captures stop at this height, so endless pages cannot run up time and memory, and the row says so (truncated). Captures are also kept under 16,000 image pixels tall and 16 megapixels in all, so mobile stops at 8,000 px, tablet at 5,000 px and a 3,840 px wide window at about 4,100 px.

## `pageTimeoutSecs` (type: `integer`):

How long a page may take to load, including waiting for your element and the extra delay. A page that is still not ready gets a free error row and is not charged. At most 60 seconds, so one page never takes more than about 90 seconds.

## `compareWithPreviousRun` (type: `boolean`):

Turn on, save as a task and schedule it: each page is compared with its last screenshot, and you get the share of pixels that changed, the changed area, a link to the previous screenshot and an image with the changes in red. The first run saves each page as its baseline. Normal runs never touch this memory.

## `minChangePercent` (type: `string`):

A page counts as changed when at least this share of its pixels changed (a pixel counts when a colour moved by more than 24 of 255, so font smoothing does not). The default 0.1% catches a changed paragraph or banner on a full page; for a price, capture the price element (then the share is of that element) or pick 'Any visible change'. Raise it, or use "Hide these elements", for pages with carousels, dates or ads.

## `onlyChangedPages` (type: `boolean`):

Monitoring only: pages that did not change get no row and no screenshot charge (only the comparison), so your dataset, sheet or Zapier flow holds changes only.

## `skipExistingOnFirstRun` (type: `boolean`):

Monitoring only: the first run saves each page's baseline without returning rows (each baseline is charged as one comparison instead of a screenshot), so alerts start with the first real change.

## `stateStoreName` (type: `string`):

Key-value store that keeps each page's last screenshot between runs (lowercase letters, digits and hyphens). Each saved task and each set of capture options keeps its own memory inside it, so the default works for most people.

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

How many pages the browser opens at the same time, at most 4. 3 suits the default 2 GB of memory.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://docs.apify.com/platform/actors"
  ],
  "fullPage": true,
  "device": "desktop",
  "format": "png",
  "jpegQuality": 80,
  "savePdf": false,
  "hideCookieBanners": true,
  "delayMs": 0,
  "scrollToLoadLazyContent": true,
  "darkMode": false,
  "maxPageHeight": 10000,
  "pageTimeoutSecs": 45,
  "compareWithPreviousRun": false,
  "minChangePercent": "0.1",
  "onlyChangedPages": false,
  "skipExistingOnFirstRun": false,
  "stateStoreName": "website-screenshot-monitor-state",
  "maxConcurrency": 3
}
```

# Actor output Schema

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

One row per page, with a link to its screenshot

# 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://apify.com",
        "https://docs.apify.com/platform/actors"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rookdev/website-screenshot-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://apify.com",
        "https://docs.apify.com/platform/actors",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("rookdev/website-screenshot-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://apify.com",
    "https://docs.apify.com/platform/actors"
  ]
}' |
apify call rookdev/website-screenshot-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rookdev/website-screenshot-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/5wCrsJ4HtA31oeOi2/builds/94hQcpvoErQpTOwx4/openapi.json
