# Website Screenshot + Change Detection — Full Page, Mobile, PDF (`saliga/website-screenshot-change-detection`) Actor

Bulk website screenshots on desktop, tablet and mobile: full page or visible area, PNG, JPEG or PDF, cookie banners hidden. Website change monitor: each run is compared with the last for website monitoring and visual regression, with changed % and a diff image. From $1 per 1,000 screenshots.

- **URL**: https://apify.com/saliga/website-screenshot-change-detection.md
- **Developed by:** [Salih Küçükkayış](https://apify.com/saliga) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 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

Take website screenshots in bulk (visible area or full page) of many web pages on desktop, tablet and mobile in one run, and monitor websites for visual changes: find out which pages changed since the last run.

**$1 per 1,000 screenshots** of the visible area ($0.90, $0.80 and $0.70 on the Starter, Scale and Business plans), **$4 per 1,000 full-page screenshots** (and PDFs). You pay only for screenshots that were captured. Failed pages are free. Change detection is included. No platform usage fees. (Plus a tiny start fee of $0.00005 per GB of run memory: $0.0002 per run at the default 4 GB.)

### Quick start

Press **Start** with the example input. It captures two pages on desktop and mobile:

```json
{
  "urls": ["https://example.com", "https://www.wikipedia.org"],
  "devices": ["desktop", "mobile"]
}
```

You get one dataset row per URL and device (trimmed here):

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "device": "desktop",
  "format": "png",
  "statusCode": 200,
  "title": "Example Domain",
  "width": 1920,
  "height": 1080,
  "fullPage": false,
  "imageUrl": "https://api.apify.com/v2/key-value-stores/…/records/…png",
  "tookMs": 2140,
  "attempts": 1,
  "error": null
}
```

To monitor pages for changes, add a monitor name and turn on change detection, then schedule the run (for example daily):

```json
{
  "urls": ["https://example.com/pricing"],
  "devices": ["desktop"],
  "fullPage": true,
  "detectChanges": true,
  "monitorName": "competitor-pricing",
  "minChangePercent": 0.5
}
```

From the second run on, each row also has `changed`, `diffPercent` and, when something changed, `diffImageUrl`.

### What you get

- **Many URLs × many devices in one run.** Pick desktop, laptop, tablet, mobile and Android. Every URL is captured on each.
- **Visible area or real full page.** For full-page captures the Actor scrolls once so lazy-loaded images appear, then captures the whole page. Pages too long for one capture (long articles, docs, feeds) are captured one screen at a time and joined, up to 50,000 px, with fixed headers shown once. Pages whose layout is exactly one screen tall with the content overflowing it (Wikipedia, many web apps) are captured in full, and a scroll lock left by a cookie banner is undone. Pages longer than the height you choose are cut there and marked `truncated`.
- **Visual change detection.** Turn on *Detect visual changes* and schedule the Actor. Every screenshot is compared with the one from the previous run, and you get:
  - `changed`: true or false
  - `diffPercent`: how much of the page changed (% of pixels)
  - `diffImageUrl`: an image with the changes highlighted in red
- **Clean shots.** Cookie and consent pop-ups (OneTrust, Cookiebot, Usercentrics, Didomi, Quantcast and more) are hidden. You can hide any other element with a CSS selector.
- **PNG, JPEG or PDF.** Light or dark theme, locale, time zone, custom headers and cookies for your own staging sites.

### Typical uses

- **Monitor competitors' pricing or landing pages** every day and get only the pages that changed.
- **Check a release** on desktop and mobile before and after a deploy.
- **Archive pages** as PNG or PDF for compliance or evidence.
- **Give an AI agent eyes** through the Apify MCP server: "take a screenshot of these pages on mobile".

### How change detection works

1. Set a **monitor name** (for example `competitor-pricing`) and turn on **Detect visual changes**.
2. The first run saves each screenshot as the baseline in a key-value store named `screenshot-baselines-<monitor name>` in your account.
3. Every later run compares the new screenshot with the baseline for the same URL and device, then saves the new one as the next baseline. Choose **Fixed first capture** to always compare with the first one instead.
4. Use **Minimum change to report** to ignore tiny changes such as a clock or a rotating banner.

Tip: add an Apify integration (Slack, email, webhook) on the run and filter the dataset for `changed = true`.

### Output

One dataset row per URL and device:

| Field | Meaning |
|---|---|
| `imageUrl` | Link to the screenshot (PNG, JPEG or PDF) |
| `url`, `finalUrl` | Requested URL and the URL after redirects |
| `device` | Device profile used |
| `statusCode`, `title` | HTTP status and page title |
| `width`, `height`, `bytes` | Image size in pixels and bytes |
| `truncated` | `true` if the page was taller than the maximum height |
| `tiled` | `true` if the full page was captured one screen at a time and joined |
| `changed`, `diffPercent`, `diffImageUrl` | Change detection results (when on) |
| `previousCapturedAt` | When the compared screenshot was taken |
| `note` | For example "first capture, saved as the baseline" |
| `tookMs`, `attempts`, `error` | Timing, tries and why it failed, if it did |

The image files are also in the run's key-value store.

### Good to know

- A page that returns an error (for example 404) is still captured and charged, because you get the image of what the page shows. Pages that do not load at all are free.
- Private and internal addresses (localhost, 10.x, 192.168.x, cloud metadata) are blocked: the page itself, every redirect, iframes and other resources. Custom proxy URLs must be http:// and must not point to such addresses.
- Sites with strong bot protection may show a challenge page instead of the content. Use the proxy option, or the result will show what the browser saw.
- You are responsible for capturing sites you are allowed to access and for respecting their terms.
- Pricing: $0.001 per visible-area screenshot, $0.004 per full-page screenshot or PDF, plus $0.00005 per GB of memory when a run starts ($0.0002 at the default 4 GB). Change detection is included. Platform usage is included.
- Full-page captures cost more because rendering and encoding a very tall image takes about 10 times the compute of the visible area.

### What it costs

| Run | Price (no discount) |
|---|---|
| 100 pages × 2 devices, visible area | $0.20 |
| 1,000 pages, visible area | $1.00 |
| 1,000 pages, full page or PDF | $4.00 |
| Daily change check of 20 full pages for 30 days | $2.40 |

### FAQ

**How do I get an alert when a page changes?** Schedule the Actor with change detection on, and add an Apify integration (Slack, email or webhook) to the schedule. Filter the dataset for `changed = true`.

**Am I charged for pages that fail?** No. Pages that do not load at all are free. A page that loads with an error status (for example 404) is captured and charged, because you get the image of what it shows.

**Can I capture pages behind a login or on a staging site?** Yes: pass `headers` or `cookies`. Private network addresses (localhost, 10.x, 192.168.x) are blocked.

**Can I get a PDF instead of an image?** Yes: set `format` to `pdf`. PDFs are charged like full-page screenshots.

**Can I run it from Make, Zapier, n8n, my own code or an AI agent?** Yes, like any Apify Actor: through Apify's integrations, the Apify API or the Apify MCP server.

### More from this developer

- [Bulk HTTP Requests](https://apify.com/saliga/bulk-http-requests): call any API once for every row of a CSV, JSON list or Apify dataset.
- [Keyword Search Volume](https://apify.com/saliga/keyword-search-volume): Google search volume, CPC and trends for a keyword list, in up to 20 countries in one run.
- [Document to Markdown](https://apify.com/saliga/document-to-markdown): PDF, Word, PowerPoint, Excel and scanned images to clean Markdown with tables and RAG chunks, $2 per 1,000 pages.
- [Audio to Text](https://apify.com/saliga/audio-to-text): audio, video and podcast feeds to timestamped text, SRT/VTT subtitles and RAG chunks with Whisper, $5 per 1,000 audio minutes.
- [Video to Scenes](https://apify.com/saliga/video-to-scenes): scene cuts, one keyframe per scene, a contact sheet and the transcript of each scene, $2 per 1,000 frames.
- [Career Site Jobs](https://apify.com/saliga/career-site-jobs): open jobs from Greenhouse, Lever, Ashby, Workable, Recruitee and Workday career sites, with a new-jobs monitor and a hiring summary per company, $1 per 1,000 jobs.

Questions or a missing feature: open an issue on the Actor page.

# Actor input Schema

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

Web pages to capture. One screenshot per URL and device.

## `devices` (type: `array`):

Capture each URL on every selected device. Desktop 1920x1080, laptop 1366x768, tablet (iPad) 820x1180, mobile (iPhone) 390x844, Android 412x915.

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

Capture the whole scrollable page instead of only the visible area. Full-page captures are priced separately because they take about 10x more compute.

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

PNG (sharp, larger), JPEG (smaller) or PDF. PDF is priced as a full-page capture.

## `detectChanges` (type: `boolean`):

Compare every screenshot with the one from the previous run of this monitor and report whether the page changed, how much (% of pixels), and an image that highlights the changes. Schedule the Actor to monitor pages. The first run only saves the baseline. PNG and JPEG only.

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

Keep separate baselines for different lists of pages, for example "competitor-pricing". Baselines are kept in a named key-value store in your account.

## `minChangePercent` (type: `number`):

Report a page as changed only when more than this share of pixels differs. Raise it to ignore small things like a clock or a rotating banner.

## `baselineMode` (type: `string`):

Previous run: each run is compared with the one before. Fixed: keep the first capture as the reference until you change the monitor name.

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

Hide common cookie and consent pop-ups (OneTrust, Cookiebot, Usercentrics, Didomi, Quantcast and more).

## `clickAcceptCookies` (type: `boolean`):

Some sites keep the page blurred until consent is given. When on, the Actor clicks a known accept button before hiding the banner.

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

CSS selectors to hide before the capture, for example chat widgets or ads: "#intercom-container", ".ad-slot".

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

When the page counts as loaded. "Network idle" is best for heavy JavaScript sites but slower.

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

Optional CSS selector to wait for before the capture.

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

Wait this long after loading, for animations to finish.

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

For full-page captures, scroll down once so lazy-loaded images appear, then capture.

## `maxHeightPx` (type: `integer`):

Very long pages are cut at this height and marked truncated. Pages taller than one capture are taken one screen at a time and joined, up to 50,000 px (60,000 image pixels on high-density devices).

## `colorScheme` (type: `string`):

Ask the site for its light or dark theme.

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

1-100. Only for JPEG.

## `deviceScaleFactor` (type: `integer`):

Override the device pixel ratio (1-3). Leave empty to use each device's default.

## `pdfFormat` (type: `string`):

Paper size for PDF output.

## `locale` (type: `string`):

Browser language, for example "de-DE" or "tr-TR".

## `timezoneId` (type: `string`):

For example "Europe/Berlin".

## `headers` (type: `object`):

Headers sent with every page request, for example an Authorization header for a staging site you own.

## `cookies` (type: `array`):

Cookies to set before loading, in Playwright format: \[{"name":"session","value":"...","domain":"example.com","path":"/"}]. Only for sites you are allowed to access.

## `proxyConfiguration` (type: `object`):

Optional. Use a proxy to capture a page as seen from another country.

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

How many pages to capture at the same time. Apify gives 1 CPU core per 4 GB of memory; raise the run memory if you raise this.

## `timeoutSecs` (type: `integer`):

Give up on a page after this many seconds.

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

Retry a page this many times when it fails to load. Failed pages are never charged.

## `debug` (type: `boolean`):

Write every step of every capture to the log. Useful when a page fails.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://www.wikipedia.org"
  ],
  "devices": [
    "desktop",
    "mobile"
  ],
  "fullPage": false,
  "format": "png",
  "detectChanges": false,
  "minChangePercent": 0.1,
  "baselineMode": "previous",
  "hideCookieBanners": true,
  "clickAcceptCookies": true,
  "waitUntil": "load",
  "delayMs": 0,
  "scrollToBottom": true,
  "maxHeightPx": 16000,
  "colorScheme": "light",
  "quality": 80,
  "pdfFormat": "A4",
  "concurrency": 2,
  "timeoutSecs": 45,
  "maxRetries": 1,
  "debug": false
}
```

# Actor output Schema

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

One row per URL and device, with a link to the image and, when change detection is on, whether the page changed.

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

All screenshots and difference images in the run's 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://example.com",
        "https://www.wikipedia.org"
    ],
    "devices": [
        "desktop",
        "mobile"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("saliga/website-screenshot-change-detection").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://example.com",
        "https://www.wikipedia.org",
    ],
    "devices": [
        "desktop",
        "mobile",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("saliga/website-screenshot-change-detection").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://example.com",
    "https://www.wikipedia.org"
  ],
  "devices": [
    "desktop",
    "mobile"
  ]
}' |
apify call saliga/website-screenshot-change-detection --silent --output-dataset

```

## MCP server setup

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

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/FIPNokyHQvD0jRegR/builds/suyTeX42EtcRscHsH/openapi.json
