Website change monitoring

Compare two URLs pixel by pixel, get a similarity score and an overlay showing what moved. The hard part is not the comparison — it is ignoring the half of the page that changes on its own.

One call

compare two URLs
curl -X POST https://api.getsnap.dev/v1/diff \
  -H "X-API-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "before": { "url": "https://example.com/pricing" },
    "after":  { "url": "https://staging.example.com/pricing" }
  }'

You get back three images and a number:

response
{
  "before_url": "https://cdn.getsnap.dev/...",
  "after_url":  "https://cdn.getsnap.dev/...",
  "diff_url":   "https://cdn.getsnap.dev/...",
  "similarity": 99.42,
  "changed_pixels": 5344,
  "total_pixels": 921600,
  "width": 1280, "height": 720,
  "credits_charged": 2
}

The diff overlay is red highlights on transparency, so you can lay it over the "after" image and see what moved rather than hunting for it. Both sides are captured at identical viewport, device and blocking settings, because two images of different sizes cannot be compared pixel by pixel at all.

The problem is noise, not detection

Detecting that two images differ is easy. The reason most visual monitoring gets switched off after a fortnight is that it reports a change every single run, and the person receiving the alerts stops reading them.

The culprits are nearly always the same handful of things:

None of those are the change you care about, and no threshold setting will separate them from one, because a moving carousel can shift more pixels than a changed price. The fix is to remove them before the comparison, not to tune the comparison afterwards:

hiding the moving parts
{
  "before": { "url": "https://example.com/pricing" },
  "after":  { "url": "https://staging.example.com/pricing" },
  "css": ".carousel, .ad-slot, .live-counter, time { visibility: hidden !important }",
  "remove_popups": true,
  "block_ads": true,
  "wait_for_network_idle": true
}

css is injected into both captures before the screenshot is taken. remove_popups and block_ads are on by default, which removes two of the five on their own.

Each side also takes its own wait_for_selector, extra_delay_ms and css, which matters when the two environments differ — a staging build that is slower to render, or a banner that only exists in production.

Choosing a threshold

There is no universal number, and anyone who gives you one has not run this against a real page. The honest method is to measure first: compare the page against itself for a week, see what the quiet days score, and set the alert below that. A page that reads 99.8 on a day when nothing happened should not alert at 99.9.

OptionDoesDefault
thresholdhow different two pixels must be to count, 0–10.1
anti_aliasignore anti-aliasing artefactstrue
include_aacount anti-aliased pixels as changesfalse
full_pagecompare the whole document, not one screenfalse

Anti-aliasing is worth understanding before you turn it off. Text edges are rendered with slightly different sub-pixel values between runs, so a naive comparison counts thousands of changed pixels on a page where nothing changed at all. Those are ignored by default. Set include_aa only if you are specifically watching for font rendering changes.

Full page, and the mismatch error

full_page compares the whole document rather than the first screen. It is the right choice for a terms page and the wrong one for a feed, because a full-page capture is as tall as the content — and if the content grew between the two captures, the two images are different sizes.

That returns a dimension_mismatch error rather than a meaningless score, which is deliberate: a comparison between a 4,000px image and a 4,600px one has no answer worth giving. If the length of the page changes, compare at a fixed viewport instead.

What this is actually used for

What it costs

Two credits for a fresh comparison, one per page captured. If either side hits the screenshot cache that side is free, so a daily check against an unchanged baseline often costs one credit rather than two. The free plan's 100 a month covers a daily comparison with room to spare.

There is also a no-signup tool that runs the same endpoint, if you would rather see the overlay before writing any code.

Frequently asked questions

Why does it report a change every run?

Something on the page moves on its own — a clock, a carousel, an ad, a relative timestamp. Hide them with injected CSS rather than raising the threshold, because a moving carousel can shift more pixels than a changed price.

What score counts as a real change?

Measure your own page for a week and set the alert below its quiet-day score. There is no number that is right for every page.

What is a dimension mismatch?

The two captures came back different sizes, usually because a full-page capture grew. Compare at a fixed viewport when the content length moves.

Does anti-aliasing cause false positives?

It would, which is why anti-aliased pixels are ignored by default. Turn that off only if font rendering is what you are watching.

Run one comparison and look at the overlay

100 captures a month, free and permanent, no card. Every option on this page works on the free plan — we meter credits, not features.

Get a free API key