Terms of service, government notices, job boards, out-of-stock products, partner pages, your own marketing site after every deploy: plenty of pages are worth watching, and checking them by hand doesn't happen. A change monitor captures each page on a schedule, compares it with the previous capture, and tells you what changed with a picture to prove it.
Capture the picture and the text together
Text diffs are the better signal: they ignore rendering noise and tell you exactly what changed. Screenshots are the better proof. Get both from one render with metadata_content:
curl -X POST "https://shotkit.net/api/take" \
-H "X-Access-Key: YOUR_ACCESS_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/terms","response_type":"json","full_page":true,"format":"jpg","metadata_content":true,"metadata_content_format":"markdown","metadata_http_response_status_code":true,"block_cookie_banners":true,"block_chats":true}' \
--fail-with-body -o shot.json{
"screenshot_url": "https://shotkit.net/cdn/files/…/….jpg",
"content": { "url": "https://shotkit.net/cdn/files/…/….md", "expires": "…", "format": "markdown" },
"http_response": { "status_code": 200 }
}const res = await take({ url, response_type: "json", full_page: true, metadata_content: true, metadata_content_format: "markdown" });
const markdown = await fetch(res.content.url).then((r) => r.text());
const previous = await db.lastSnapshot(url);
if (previous && previous.markdown !== markdown) {
await alert({ url, diff: diffLines(previous.markdown, markdown), screenshot: res.screenshot_url });
}
await db.saveSnapshot(url, { markdown, screenshot: await download(res.screenshot_url) });Run it from any scheduler: a cron job, GitHub Actions on a schedule, Vercel Cron or a queue worker. Files on the CDN expire, so download what you want to keep.
Watch only the part that matters
Most false alarms come from parts of the page you don't care about: dates, counters, rotating banners, recommendations.
selectorlimits both the screenshot and the Markdown to one element, such asmainor#pricing.hide_selectorsremoves noisy elements inside it.- With pixel diffs, set a threshold so a few changed pixels don't alert; see visual regression testing.
Detect breakage, not only changes
metadata_http_response_status_coderecords the status; a 404 or 500 fails the request withhost_returned_error, which is itself worth an alert.fail_if_content_missingfails when text that should be there is gone, such as "Add to cart" or your product's name.- Don't turn on
cachefor monitoring, or you'll compare a page with itself until the cache expires.
Frequently asked questions
How often can I check a page?
As often as your plan allows: each check is one screenshot. Ten pages checked every hour is about 7,200 a month; checking daily is 300.
Can I monitor pages behind a login?
Yes. Pass a session with cookies or a token with authorization.
Why compare Markdown instead of HTML?
HTML changes with every deploy: class names, script tags, tracking attributes. Markdown keeps only the content people read, so a diff means something changed for readers.
Does a failed check count toward my quota?
No. Failed requests, including pages that return errors, don't count.