Monitors
Detect page changes on any URL — by screenshot or scrape — and receive webhook notifications.
Monitors periodically capture a URL and notify you when the page changes. Each monitor uses one
of two capture types: screenshot (perceptual/visual change detection) or scrape (content and
markdown change detection). Use them for price tracking, content alerts, uptime monitoring, or
competitor surveillance.
Create a monitor
curl -X POST https://api.bytekit.com/v1/monitors \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"interval_type": "hourly",
"webhook_url": "https://your-server.example.com/webhooks/bytekit"
}'| Field | Description |
|---|---|
url | The page to monitor (required) |
interval_type | Check frequency — one of hourly, daily, weekly, or cron (required) |
webhook_url | HTTPS webhook endpoint to receive notifications. http:// is rejected with a 400 invalid_url (reason scheme_unsupported) (required) |
cron_expression | 5-field cron expression. Required when interval_type is cron |
type | Capture mode — screenshot (default) or scrape |
change_threshold | Percentage of change required to trigger a notification (0–100, default 5). Screenshot monitors only |
notify_on | change (default — notify only when content changed) or every (notify on every capture) |
webhook_secret | Optional secret used to sign webhook deliveries |
webhook_headers | Optional custom headers (max 20) to include on each webhook delivery |
A successful response returns the monitor object with an id prefixed mon_:
{
"id": "mon_...",
"type": "screenshot",
"url": "https://example.com/pricing",
"status": "active",
"interval_type": "hourly",
"cron_expression": null,
"next_capture_at": "2026-01-01T01:00:00Z",
"notify_on": "change",
"webhook_url": "https://your-server.example.com/webhooks/bytekit",
"captures_count": 0,
"changes_count": 0,
"created_at": "2026-01-01T00:00:00Z"
}To monitor on a schedule, set interval_type to cron and supply a 5-field cron_expression:
curl -X POST https://api.bytekit.com/v1/monitors \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"interval_type": "cron",
"cron_expression": "0 9 * * 1-5",
"webhook_url": "https://your-server.example.com/webhooks/bytekit"
}'List monitors
curl https://api.bytekit.com/v1/monitors \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE"Get a monitor
curl https://api.bytekit.com/v1/monitors/mon_... \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE"Update a monitor
curl -X PATCH https://api.bytekit.com/v1/monitors/mon_... \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"interval_type": "daily"}'You can update any subset of the monitor's mutable fields — for example interval_type,
cron_expression, webhook_url, notify_on, change_threshold, or status
(active / paused). Switching to a cron schedule requires a cron_expression:
curl -X PATCH https://api.bytekit.com/v1/monitors/mon_... \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"interval_type": "cron", "cron_expression": "*/30 * * * *"}'Delete a monitor
curl -X DELETE https://api.bytekit.com/v1/monitors/mon_... \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE"Monitor captures
Each time a monitor fires it records a capture event — on every run, whether or not the
content changed. Each capture carries a has_change flag marking whether the content differed
from the previous capture. Webhook delivery is separate: the capture is always recorded, but
whether a webhook is sent depends on has_change and the monitor's notify_on setting (see
Webhook payload below). Retrieve the capture history with:
curl https://api.bytekit.com/v1/monitors/mon_.../captures \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE"Each capture in the list represents one capture event and includes change metadata — the
timestamp, whether the content changed (has_change), the percentage of content that changed
(change_pct), and the perceptual hash distance from the previous capture. Captures record
that the content changed and by how much; they do not include a line-by-line textual diff.
First-capture behavior (silent baseline for both types)
The very first capture of a monitor has no previous capture to compare against. Both monitor types treat that first run as a silent baseline — one universal "first capture" behavior, no per-type divergence:
| Monitor type | First capture | has_change | change_pct | Webhook on first capture |
|---|---|---|---|---|
scrape | Silent baseline | false | null | No monitor.change_detected-equivalent webhook (the baseline is just recorded) |
screenshot | Silent baseline | false | null | No monitor.change_detected webhook (the baseline is just recorded) |
Neither a scrape nor a screenshot monitor alerts you on its first run — both only alert
once a subsequent capture differs (scrape by exact content-hash inequality, screenshot by at
least change_threshold). A monitor with notify_on: "every" still sends a monitor.captured
webhook on the first capture — that is the per-capture notification, not a change alert, and it
fires for both monitor types. From the second capture onward both types behave the same way each
new capture is compared against the previous one — scrape by exact SHA-256 content equality,
screenshot by perceptual-hash distance against change_threshold.
Existing scrape monitors: if you created a scrape monitor before this change shipped, its
first post-deploy capture will no longer fire a monitor.change_detected-equivalent webhook —
the first capture after the change is now the silent baseline, matching screenshot monitors.
Steady-state alerting (second capture onward) is unaffected.
Webhook payload
When a monitor fires, ByteKit sends a POST to your webhook_url. Every delivery carries an
X-ByteKit-Event header naming the event type — dispatch on that header rather than inspecting
the body shape. The event type and payload depend on the monitor's type.
Screenshot monitors (type: "screenshot", default)
Screenshot captures send monitor.change_detected when the page changed (X-ByteKit-Event: monitor.change_detected) and monitor.captured when there was no change but
notify_on: "every" is set (X-ByteKit-Event: monitor.captured). The body carries the capture
metadata:
{
"monitor_id": "mon_...",
"screenshot_id": "ss_...",
"has_change": true,
"change_pct": 42.0,
"hash_distance": 18,
"url": "https://example.com/pricing"
}| Field | Description |
|---|---|
has_change | true when the captured content differs from the previous capture |
change_pct | Percentage of content that changed (0–100) |
hash_distance | Perceptual-hash distance from the previous capture |
screenshot_id | The screenshot that triggered the notification |
Scrape monitors (type: "scrape")
Scrape captures send scrape.completed (X-ByteKit-Event: scrape.completed) with the full
scrape result envelope inline:
{
"success": true,
"type": "scrape.completed",
"id": "mon_...",
"scrape_id": "sc_...",
"has_change": true,
"change_pct": 100.0,
"metadata": {},
"scrape": {
"status": "success",
"id": "sc_...",
"url": "https://example.com/pricing",
"final_url": "https://example.com/pricing",
"content_length": 1256,
"status_code": 200,
"formats": {
"markdown": "..."
}
}
}| Field | Description |
|---|---|
has_change | true when the captured content differs from the previous capture |
change_pct | Percentage of content that changed (0–100) |
scrape | Full scrape result envelope (same shape as POST /v1/scrape response) |
Your endpoint must respond with 2xx within 10 seconds. Failed deliveries are retried with
exponential backoff.
Next steps
- API Reference: Monitors — full endpoint schema
- Errors — error codes returned by monitor endpoints