How to compare Screenshotlayer screenshots to detect website changes
Screenshotlayer captures website screenshots but does not compare them. Learn how to capture consistent images, compute visual diffs, and reduce false alarms.
Direct answer: Screenshotlayer generates website screenshots, but its documented API does not compare two screenshots, calculate a visual diff, or alert you when a page changes. Use it to capture a baseline and later image, then compare those files with an image-diff tool or script. The reliable part is consistency: keep the capture URL and settings fixed, account for caching, and review detected differences for dynamic content before calling them real changes.
This guide uses Python and Pillow for a runnable pixel-difference workflow. It also includes cURL and Node.js capture examples. Screenshotlayer can capture PNG by default, with JPEG and GIF also documented; the API offers viewport, full-page, delay, cache TTL, user-agent, language, CSS and export controls. Confirm the current API documentation for account-specific options and parameters before deploying. Screenshotlayer documentation · Screenshotlayer FAQ.
1. Understand what is being compared
A screenshot comparison is an image comparison. It answers whether the rendered pixels differ between two captures. It does not by itself explain why they differ or whether the difference matters. A changed banner, live clock, rotating ad, personalization, animation, or delayed content can produce a diff even when the content you care about is unchanged.
Screenshotlayer is the capture stage. Its reviewed product and FAQ material documents screenshot generation, formats and capture options, but not image-to-image comparison. Treat the baseline and subsequent screenshot files as inputs to your own diff step.
2. Choose a stable capture profile
Define one capture profile and reuse it for every run. Changes in capture conditions can change pixels even if the website has not changed; holding these settings constant is a workflow recommendation based on how rendering works.
| Setting | How to keep comparisons useful |
|---|---|
| Target URL | Use the same canonical URL, including query parameters and trailing-slash behavior where relevant. Redirects can change page content. |
| Viewport and scope | Keep viewport dimensions fixed. Decide whether to compare the viewport or full-page capture and use the same choice every time. |
| Output format | Prefer PNG for pixel comparisons because it avoids the lossy compression artifacts common with JPEG. Screenshotlayer documents PNG as the default and JPEG and GIF as alternatives. |
| Timing | Use the documented delay option if the page needs time for animations or effects. Keep the delay stable and long enough for relevant content to settle. |
| Headers and rendering | Keep custom User-Agent and Accept-Language settings consistent. Use the same CSS override and any other capture settings on both runs. |
| Freshness | Screenshotlayer’s FAQ states the default screenshot cache duration is 2,592,000 seconds (30 days); it says a shorter custom TTL can be specified. For monitoring, set a suitable lower TTL or use the documented refresh control where available, then verify the returned image is fresh. |
These controls are documented in the Screenshotlayer product material and FAQ. Pricing, request quotas and feature availability can change, so verify current terms before selecting a plan.
3. Capture baseline and current screenshots
Store the baseline as a versioned artifact, along with the capture profile and timestamp. The capture URL below follows Screenshotlayer’s documented API pattern. Pass your access key securely through an environment variable or secret manager; do not commit credentials.
cURL
export SCREENSHOTLAYER_ACCESS_KEY="YOUR_ACCESS_KEY"
curl -fG "https://api.screenshotlayer.com/api/capture" \
--data-urlencode "access_key=${SCREENSHOTLAYER_ACCESS_KEY}" \
--data-urlencode "url=https://example.com" \
--data-urlencode "viewport=1440x900" \
--data-urlencode "format=PNG" \
--data-urlencode "ttl=60" \
-o current.png
Use the same query parameters when generating the baseline file. Screenshotlayer’s FAQ says a lower TTL than the default may be requested; a TTL of 60 seconds is an example, not a guarantee of immediate freshness. Do not assume that a successful HTTP response alone proves the body is a valid screenshot: inspect the response and resulting image before storing it.
Python capture (requests)
import os
import requests
key = os.environ["SCREENSHOTLAYER_ACCESS_KEY"]
params = {
"access_key": key,
"url": "https://example.com",
"viewport": "1440x900",
"format": "PNG",
"ttl": 60,
}
response = requests.get(
"https://api.screenshotlayer.com/api/capture",
params=params,
timeout=90,
)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
if "image/" not in content_type:
raise RuntimeError(f"Expected an image response, got {content_type!r}: {response.text[:500]}")
with open("current.png", "wb") as image_file:
image_file.write(response.content)
Node.js capture (built-in fetch, Node 18+)
import { writeFile } from "node:fs/promises";
const params = new URLSearchParams({
access_key: process.env.SCREENSHOTLAYER_ACCESS_KEY,
url: "https://example.com",
viewport: "1440x900",
format: "PNG",
ttl: "60",
});
const response = await fetch(`https://api.screenshotlayer.com/api/capture?${params}`);
if (!response.ok) {
throw new Error(`Screenshot request failed: HTTP ${response.status} ${await response.text()}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.startsWith("image/")) {
throw new Error(`Expected image response, got ${contentType}: ${(await response.text()).slice(0, 500)}`);
}
await writeFile("current.png", Buffer.from(await response.arrayBuffer()));
Replace example.com with your target. If your account’s current documentation requires a different host, parameter spelling, or output option, use the current official documentation rather than assuming an undocumented variant.
4. Compare the images with Python
The script below checks that the files decode and have matching dimensions, then writes a difference image and reports the fraction of pixels whose RGB values changed beyond a configurable per-channel tolerance. It needs Python 3 and Pillow: install Pillow with python -m pip install Pillow. Save as diff_screenshots.py.
from PIL import Image, ImageChops
import sys
if len(sys.argv) != 3:
raise SystemExit("Usage: python diff_screenshots.py baseline.png current.png")
baseline = Image.open(sys.argv[1]).convert("RGB")
current = Image.open(sys.argv[2]).convert("RGB")
if baseline.size != current.size:
raise SystemExit(
f"Image sizes differ: baseline={baseline.size}, current={current.size}. "
"Capture both with the same viewport and page scope."
)
# Ignore small per-channel changes (for example, minor rendering noise).
tolerance = 12
diff = ImageChops.difference(baseline, current)
pixels = diff.load()
changed = Image.new("L", diff.size, 0)
changed_pixels = changed.load()
count = 0
for y in range(diff.height):
for x in range(diff.width):
r, g, b = pixels[x, y]
if max(r, g, b) > tolerance:
changed_pixels[x, y] = 255
count += 1
fraction = count / (diff.width * diff.height)
# Amplify differences for visual inspection; the threshold mask is also saved.
diff.save("difference.png")
changed.save("changed-mask.png")
print(f"Changed pixels: {count} / {diff.width * diff.height} ({fraction:.2%})")
print("Wrote difference.png and changed-mask.png")
Run it with python diff_screenshots.py baseline.png current.png. The tolerance is a starting point, not a universal setting. A low tolerance is sensitive to subtle shifts and antialiasing; a high tolerance can hide small but meaningful changes. Inspect the generated images and choose a threshold based on the page and the kind of change you need to detect.
Interpreting the result
- If dimensions differ, fix the capture profile before comparing. Resizing one image may conceal a viewport or full-page capture mismatch.
- Open
difference.pngandchanged-mask.pngto locate changed areas. The script does not determine whether a change is important. - Set an alert threshold based on changed pixel fraction or on regions of interest. Treat it as a review trigger, not proof of a defect.
- When the diff is noisy, identify the source: dynamic regions, capture timing, cache freshness, font loading, or a genuinely changed page.
5. Reduce false positives and missed changes
- Dynamic content: clocks, ad slots, rotating recommendations, counters and personalized content can change between runs. Masking regions is possible in a separate diff pipeline, but ensure the mask does not cover the content you intend to monitor.
- Late loading: use a consistent delay for animations and effects. A delay can help, but it does not guarantee that every page’s asynchronous requests have finished.
- Cookie banners and overlays: consistent consent state matters. A banner appearing on only one run can dominate the diff. Establish a repeatable state and document it.
- Fonts and rendering: a font that loads late or a different browser rendering environment can shift text and create broad pixel changes. Keep capture conditions stable.
- Full-page length: content growth changes image dimensions; treat this as a meaningful structural signal if page length matters, or compare a stable viewport/region if it does not.
- Thresholds: pixel thresholds can suppress small rendering noise, but aggressive thresholds may miss subtle regressions. Review representative runs before automating alerts.
6. Run the workflow reliably
- Keep a named capture profile with URL, viewport, page scope, format, delay, headers, CSS and cache settings.
- Save baseline and current images with timestamps and profile identifiers. Keep enough history to compare against more than one prior run if needed.
- Validate HTTP status, response content type, file decodability and image dimensions before replacing a known-good baseline.
- Retry transient request failures with bounded exponential backoff and a maximum attempt count. Avoid retrying authentication or invalid-parameter errors unchanged.
- Separate capture failure from detected page change. A missing or invalid screenshot must not be reported as a site visual change.
- For alerting, require a threshold and optionally a repeat observation before notifying. Preserve the images with the alert so a person can inspect the cause.
Full-page images can be large and can make pixel-by-pixel processing slower. Compare only the required page scope, avoid repeatedly downloading identical cached results, and keep a retention policy for old captures. Screenshotlayer’s FAQ describes plan quotas and overage billing; check the current pricing and overage terms before estimating monitoring cost. Its FAQ also says public uptime statistics are not offered and invites users to request recent reports, so design monitoring to record failures and avoid treating a single missed capture as a website change.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Baseline and current are identical despite known page updates | A cached screenshot was returned, or the target URL resolves to cached content. | Use a lower documented TTL or current documented refresh option; save timestamps and verify each returned image. |
| Images have different dimensions | Viewport, full-page behavior, responsive layout, or page length differs. | Use the same viewport and capture scope. Decide explicitly how page-height changes should be handled. |
| Large diff with no obvious site change | Dynamic content, banner state, late fonts, animation timing, or language/user-agent variation. | Stabilize headers and delay, control consent state, inspect the diff, and mask known regions carefully. |
| Image decoder says file is invalid | The response may contain an API error body or an intermediary response rather than image bytes. | Check HTTP status and Content-Type, log a limited error-body excerpt securely, and do not overwrite the baseline. |
| API rejects the request | Missing/invalid access key, unsupported parameter, malformed URL or account restriction. | Check credentials and the current official API documentation; do not expose keys in logs or source control. |
| Capture misses content below the fold | Viewport capture used where full-page capture was needed, or content is loaded only after scrolling. | Choose the documented full-height capture behavior when appropriate. Confirm the page’s lazy content appears consistently. |
| Diff script reports too many changed pixels | Threshold too sensitive or the images have global rendering differences. | Inspect the mask, raise tolerance cautiously, and first fix capture inconsistency rather than tuning away meaningful changes. |
| Diff misses a visible small change | Threshold too high or a region mask covers it. | Lower tolerance, reduce masked areas, and test against known small changes. |
| Monitoring requests become slow or costly | Excess capture frequency, unnecessary full-page images, concurrency limits or plan quota/overages. | Set a cadence appropriate to the site, track volume, reduce scope where suitable, and verify current plan terms and capacity. |
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-call capture endpoint can replace the capture portion of this workflow; you still compare the saved images with your diff logic. See the ScreenshotNeo API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month, no card required.
FAQ
Does Screenshotlayer detect visual changes by itself?
The reviewed official materials document screenshot capture, not screenshot-to-screenshot diffing or change alerts. Capture the images with Screenshotlayer and use a separate comparison step.
Should I compare PNG or JPEG screenshots?
PNG is generally the more suitable input for pixel comparison because it avoids JPEG’s lossy compression artifacts. Screenshotlayer documents PNG as the default and JPEG and GIF as other output formats.
Can a pixel diff prove that a website is broken?
No. It identifies image differences. A person or application-specific rules must decide whether the changed region is expected, important, or an error.
How frequently should I capture a page?
Choose a cadence that matches how often relevant content changes and your request budget. The appropriate interval depends on the site and should account for the API plan and cache settings.
What should I store with each screenshot?
At minimum, retain the capture timestamp, URL, profile settings, image dimensions, and a link or identifier for the baseline used. This makes a diff reproducible when someone reviews an alert.


