# Website Screenshot & Visual Change Monitor — Full Page, Mobile (`northbell/website-screenshot-monitor`) Actor

Website screenshots in bulk, $2 per 1,000: full page or one element, desktop, tablet or mobile, PNG or JPEG, cookie banners hidden. Run it again and each page is compared with last time: changed percent and a diff image with the changed pixels in red. Respects robots.txt.

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

## Pricing

from $1.60 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

## Website Screenshot & Visual Change Monitor — Full Page, Mobile ($2 per 1,000)

Paste a list of web addresses and get one **screenshot per page**: **full page** or a single element, on **desktop, laptop, wide, tablet or mobile**, as **PNG or JPEG**, with **cookie banners hidden** (nothing is clicked or accepted). Each dataset row links to its image and also tells you the page's **title, HTTP status, final address after redirects and load time**.

Run it again — by hand or on a schedule — and every page is **compared with its capture from the last run**: the row says `new`, `changed` or `unchanged`, **how much of the page changed (%)**, and links a **diff image** with the changed pixels in red. Turn on *Return only new and changed pages* and a daily run returns (and charges) only the pages that actually changed.

**Try it now — the input is already filled in.** Press **Start**: the two example pages come back as full-page PNGs in about 25 seconds, for $0.014 (measured on 28 Oct 2026). Press **Start** again and each row says whether the page changed. Three rows from our test runs that day (Wikipedia added):

| `url` | `changeStatus` (1st run → 2nd run) | `changedPercent` | `imageWidth` × `imageHeight` | `bytes` | `loadSeconds` |
| --- | --- | --- | --- | --- | --- |
| https://example.com/ | new → unchanged | 0 | 1280 × 800 | 72,616 | 0.2 |
| https://apify.com/ | new → unchanged | 0 | 1280 × 10,036 | 1,026,699 | 1.1 |
| https://www.wikipedia.org/ | new → changed | 0.12 | 1280 × 1,103 | 246,438 | 0.5 |

`screenshotUrl` opens the image directly (a signed link, no login needed); a changed page also gets `diffUrl`, the same page with the changed pixels in red.

### What you get

| Field | What it is |
|---|---|
| `url`, `finalUrl`, `httpStatus` | the address you gave, where it ended up, and the status it answered |
| `screenshotUrl` | link to the image (PNG or JPEG) in the run's key-value store |
| `title` | the page's title |
| `changeStatus` | `new` (first capture), `changed` or `unchanged` since the last run; `null` with comparison off |
| `changedPercent` | share of pixels that differ from the last capture (0–100) |
| `diffUrl` | link to the diff image (changed pixels in red), only when the page changed |
| `sizeChanged` | `true` when the page got taller, shorter or wider |
| `previousScreenshotAt` | when the capture it was compared with was taken |
| `imageWidth`, `imageHeight`, `bytes`, `format` | the image itself |
| `device`, `viewport`, `fullPage`, `selector` | how it was captured |
| `truncated` | `true` when the page was taller than 16,000 pixels and was cut there |
| `loadSeconds`, `loadTimedOut` | how long the page took; `true` when the timeout ran out and what was drawn was captured |
| `contentHash` | a fingerprint of the image (same hash = identical image) |

Pages that could not be captured come back as free `error` rows with an `errorKind` (`not-found`, `server-error`, `bot-check`, `blocked-by-robots`, `selector-not-found`, `network` …) and a plain-English `message`. A free `summary` row at the end counts screenshots, new, changed, unchanged and failed.

### Use it for

- **Competitor and pricing page watch** — a daily capture of rivals' pricing and landing pages; `changedOnly` returns just the ones that changed, with the diff image showing where
- **Your own site after a deploy** — catch a broken layout on desktop and mobile before customers do
- **Defacement and compliance checks** — proof of what a page looked like on a given day, with a fingerprint
- **Thumbnails and archives** — bulk captures of a list of sites for a directory, a report or a deck

### How the comparison works

- The capture is kept in a key-value store named `website-screenshot-monitor-state` **in your own Apify account**, keyed by the address, device, size and selector. Change any of these and that page counts as `new` once.
- Identical images are `unchanged` at once (same fingerprint). Otherwise the two images are compared pixel by pixel; a taller or shorter page counts the extra part as changed.
- Pages with a clock, a rotating banner or ads change a little every time: raise **Change threshold** (for example to 2%) or hide those parts with **Also hide these elements**.
- One capture per page is stored, replaced on each run. Delete the store to start over.

### What it does not do

- It opens **public pages only**: no login, no cookies, no headers. Local and private network addresses are refused.
- It reads each site's **robots.txt** and skips pages it does not allow (a free error row says so).
- It does **not get around bot checks** (Cloudflare challenges, CAPTCHAs): such pages come back as a free `bot-check` error row instead of a picture of the check.

### Price

**$2.00 per 1,000 screenshots**, plus $0.01 per run. Error rows, the summary row and — with *Return only new and changed pages* — unchanged pages are free. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing.

### Input example

```json
{
  "urls": ["https://apify.com", "https://example.com"],
  "device": "mobile",
  "fullPage": true,
  "changedOnly": true
}
```

Returns one row per page with `screenshotUrl`, `changeStatus` and `changedPercent`. Rows have `type`: `screenshot`, `error` or `summary`.

# Actor input Schema

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

One web address per line, for example https://apify.com. "example.com" works too (https:// is added). Up to 500 per run. Each page becomes one PNG or JPEG image in the run's key-value store, with its link in the dataset row.

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

The screen the page is opened on. Desktop is 1280x800, Laptop 1440x900, Wide 1920x1080, Tablet an iPad (820x1180 at 2x) and Mobile an iPhone (390x844 at 2x, with the phone's browser name so sites send their mobile page). Custom uses the width and height below.

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

On: the whole page from top to bottom (up to 16,000 pixels tall; taller pages are cut there and the row says truncated: true). Off: only what fits on the screen.

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

Capture one part of the page instead, for example header, #pricing or .product-card. The first match is captured. Leave empty for the page.

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

PNG keeps text sharp. JPEG files are several times smaller for long pages with photos.

## `quality` (type: `integer`):

1 to 100. Only used for JPEG.

## `compareWithLastRun` (type: `boolean`):

On: each page is compared with its capture from your previous run (same address, device and size), and the row gets changeStatus (new, changed or unchanged), changedPercent and, when it changed, a diff image with the changed pixels in red. Turn it off for one-off captures.

## `changedOnly` (type: `boolean`):

For a scheduled monitor: pages that look the same as last time are left out of the dataset and are not charged. Their capture is still kept for the next comparison.

## `changeThresholdPercent` (type: `string`):

A page counts as changed when more than this share of its pixels differs, for example 0.1 (the default) or 2 to ignore small things like a clock or a rotating banner. 0 counts any difference.

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

Hides the common consent pop-ups (OneTrust, Cookiebot, Usercentrics, Didomi, Quantcast and others) before capturing. Nothing is clicked or accepted.

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

CSS selectors to hide before capturing, one per line, for example a chat widget (#intercom-container) or a live ticker that would make every run look changed.

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

Asks the page for its dark colour scheme (sites that support it).

## `scrollToBottom` (type: `boolean`):

For full-page captures: scrolls to the bottom and back (at most 8 seconds) so images that load on scroll are shown.

## `waitUntil` (type: `string`):

When the page counts as ready to capture.

## `delaySeconds` (type: `integer`):

Waits this long after the page is ready, for animations or late content. 0 to 30.

## `timeoutSeconds` (type: `integer`):

The longest wait for a page. When it runs out, whatever has been drawn is captured and the row says loadTimedOut: true.

## `width` (type: `integer`):

Only with Device = Custom size. 320 to 3840.

## `height` (type: `integer`):

Only with Device = Custom size: the screen height (a full-page capture is taller). 320 to 4000.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://example.com"
  ],
  "device": "desktop",
  "fullPage": true,
  "format": "png",
  "quality": 80,
  "compareWithLastRun": true,
  "changedOnly": false,
  "changeThresholdPercent": "0.1",
  "hideCookieBanners": true,
  "darkMode": false,
  "scrollToBottom": true,
  "waitUntil": "load",
  "delaySeconds": 0,
  "timeoutSeconds": 45
}
```

# Actor output Schema

## `screenshots` (type: `string`):

Image link, change status, changed percent, diff image link, title and HTTP status of every captured page.

## `files` (type: `string`):

Every screenshot and diff image of this run in the key-value store.

# 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://example.com"
    ]
};

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

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

```

## MCP server setup

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