Site Watch API
Site Watch API, webhooks and AI connector.
See what's up or down from your own code, add websites, post status page notices and get told the moment something stops working. Or ask Claude “is anything down?”. It all runs on your own Site Watch, in your own Cloudflare account.
1Get started
The API comes with Site Watch 1.2.0 and later. It lives on your own install:
https://watch.yourdomain.co.uk/api/v1
- Open the Account tabSign in to Site Watch and open Account. Your API address is under API, webhooks and AI connector, ready to copy.
- Make a keyGive it a name, tick It can change things if your script needs to add or pause monitors, and copy the key. It's shown once, and starts with
watch_. - Send it with each requestIn an Authorization header, like this:
curl https://watch.yourdomain.co.uk/api/v1/monitors \ -H "Authorization: Bearer watch_••••••••••••••••••••••••••••••••"
2Keys and permissions
Make a separate key for each script or app, so you can delete one without stopping the others. You can have up to 10.
| Key | What it allows |
|---|---|
| Read only | Everything you watch, uptime, outages, alerts and status pages. |
| Read and change | All of that, plus adding, checking, pausing and removing monitors, and posting and resolving status page notices. |
3Every request
| Request | Needs | What it does |
|---|---|---|
GET /monitors | Read | Everything you watch, with status, uptime over 30 and 90 days, response time, certificate expiry and domain renewal. |
GET /monitors/{id} | Read | One monitor, with its last 10 outages. |
POST /monitors | Change | Start watching something. kind is website, mail, port or heartbeat. See the example below. |
POST /monitors/{id}/check | Change | Check it now. |
POST /monitors/{id}/pause | Change | Pause it, for example {"minutes": 60} during maintenance. |
POST /monitors/{id}/resume | Change | Start checking again. |
DELETE /monitors/{id} | Change | Stop watching it. |
GET /alerts | Read | The latest alerts, newest first. Takes limit (up to 100). |
GET /pages | Read | Your status pages, which monitors each shows, and notices from the last 14 days. |
POST /pages/{id}/notices | Change | Post a notice: {"title": "…", "body": "…", "impact": "minor"}. Impact is minor, major or maintenance. |
POST /notices/{id}/resolve | Change | Mark a notice resolved. |
A monitor's status is up, down, cert (a certificate problem), blocked (the server turned the checks away), unsure (one failed check), paused or checking.
4Examples
Is anything down?
curl https://watch.yourdomain.co.uk/api/v1/monitors \ -H "Authorization: Bearer $WATCH_KEY"
The answer (shortened)
{
"monitors": [
{
"id": "3f9a1c2b7d4e8a60",
"kind": "website",
"label": "yourdomain.co.uk",
"url": "https://yourdomain.co.uk/",
"status": "up",
"responseMs": 182,
"uptime": { "30d": 99.98, "90d": 99.95 },
"checkedAt": "2026-11-04T17:00:00Z",
"certificate": { "expires": "2027-01-20T23:59:59Z", "daysLeft": 77, "renewsItself": true },
"domain": { "name": "yourdomain.co.uk", "expires": "2027-06-01T00:00:00Z", "daysLeft": 209 }
}
]
}Start watching a website
curl https://watch.yourdomain.co.uk/api/v1/monitors \
-H "Authorization: Bearer $WATCH_KEY" \
-H "Content-Type: application/json" \
-d '{ "kind": "website", "url": "https://shop.yourdomain.co.uk/", "every": 5 }'every is how often to check, in minutes: 1, 5, 10, 15, 30 or 60. A mail server or port takes host and port instead of url. A heartbeat takes a name and how often the job runs, and the answer gives you its pingUrl.
Pause during maintenance
curl -X POST https://watch.yourdomain.co.uk/api/v1/monitors/3f9a1c2b7d4e8a60/pause \
-H "Authorization: Bearer $WATCH_KEY" \
-H "Content-Type: application/json" \
-d '{ "minutes": 30 }'In JavaScript
const res = await fetch(`${WATCH_API_URL}/monitors`, {
headers: { Authorization: `Bearer ${process.env.WATCH_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error);
const { monitors } = await res.json();
const down = monitors.filter((m) => m.status === "down");5Webhooks
Add Your own webhook on the Alerts tab, with its https:// address. Site Watch posts to it every time it sends an alert.
| Event | When it's sent |
|---|---|
monitor.down | Something stopped working. |
monitor.up | It's working again. |
monitor.flapping | It keeps going down and coming back. |
monitor.blocked | The server is turning the checks away. |
monitor.cert | The website's certificate isn't valid. |
cert.expiring | The certificate runs out soon and doesn't renew itself. |
domain.expiring | The domain's registration runs out soon. |
alert.test | The Send a test button. |
What Site Watch sends
{
"id": "evt_8c1d2e3f4a5b6c7d8e9f0a1b",
"type": "monitor.down",
"created": "2026-11-04T17:02:11Z",
"app": "Site Watch",
"monitor": { "id": "3f9a1c2b7d4e8a60", "kind": "website", "label": "yourdomain.co.uk", "url": "https://yourdomain.co.uk/", "host": null, "port": null },
"subject": "yourdomain.co.uk is down",
"text": "yourdomain.co.uk stopped answering at 17:02 UTC. …",
"urgent": true,
"url": "https://watch.yourdomain.co.uk/"
}Each request also carries Site-Watch-Event, Site-Watch-Delivery (an id for this post) and Site-Watch-Signature. Answer with any 2xx status within 10 seconds. Site Watch also sends to Slack, Teams, Discord, Google Chat and email, so you only need a webhook for your own code.
Checking a webhook is genuine
Each webhook has its own signing secret (starting whsec_), shown when you add it. The app signs <t>.<body> with HMAC-SHA256 using that secret, and sends the result in the Site-Watch-Signature header as t=1760520601,v1=5f2b…. To check a request, work out the same signature from the raw body, compare it with v1 and refuse anything more than five minutes old.
Node.js
import crypto from "node:crypto";
function isFromSiteWatch(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.v1 || age > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Python
import hashlib, hmac, time
def is_from_site_watch(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if "v1" not in parts or abs(time.time() - int(parts["t"])) > 300:
return False
signed = parts["t"].encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Use the body exactly as it arrived. Turning the JSON into an object and back into text can change it, and then the signature won't match.
6AI connector
Add Site Watch to Claude, ChatGPT or any MCP client. In Claude, open Settings › Connectors › Add custom connector and paste:
https://watch.yourdomain.co.uk/mcp
Claude asks you to sign in to Site Watch once. Then ask things like “is anything down?” or “what was my uptime last month?”. It can only read. Disconnect AI apps on the Account tab signs it out. Apps that let you add a header can send a watch_ key instead of signing in.
7Errors and limits
Anything that goes wrong comes back with a status, a sentence you can show to a person and a link to this page:
{"error": "This key can only read. Make a key that can change things on the Account tab.", "docs": "https://paulobrien.dev/watch/api/"}| Status | What it means |
|---|---|
400 | Something in the request needs fixing, such as a key put in the web address. |
401 | No key, or a key that has been deleted. |
403 | The key can only read. |
404 | Not found, or a request the API doesn't have. |
405 | Use GET to read, POST to make or change and DELETE to remove. |
429 | Each key can make 60 requests a minute. Retry-After says how long to wait. |
Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining. There are no browser (CORS) headers on purpose: keep keys on your server, or in tools like Zapier, Make or n8n.
Questions or ideas? Ask in GitHub Discussions, or open an issue for a bug.
Stay in the loop
Subscribe for updates.
Leave your email and I'll tell you when there's a new version of one of my apps, or a new project to try. Nothing else.