API & webhooks
Everything you can do in the app is available over a JSON API. Personal API tokens and outgoing webhooks are part of the Pro plan.
Authentication
Create a token in Settings → Developer. It's shown once and starts with wt_. Send it as a bearer token:
curl https://watch-tower-seven.vercel.app/v1/me/watches \
-H "Authorization: Bearer wt_…"Errors share one shape: {"error": {"code", "message", "details", "request_id"}}. Rate-limited requests return 429 with Retry-After; plan limits return 402 with details.metric.
Create a watch in plain English
POST /v1/intake {"query": "Tell me when SSC CGL 2026 results are out"}
GET /v1/intake/{id} → status: running | awaiting_user | proposed | created | rejected
POST /v1/intake/{id}/answer {"answers": {"<question id>": "…"}}
POST /v1/intake/{id}/confirm {"confirm": true, "edits": {"interval_minutes": 120}}Send an Idempotency-Key header on POST /v1/intake and POST /v1/watchesso retries never create duplicates.
Useful endpoints
| Endpoint | What it does |
|---|---|
GET /v1/me/watches | Your watches and subscriptions |
GET /v1/watches/{id}/state | Live view: schedule, last run with steps, last alert, 7-day stats |
GET /v1/watches/{id}/runs | Check history (cursor pagination) |
POST /v1/watches/{id}/run | Check now (counts against a daily quota) |
POST /v1/watches/{id}/pause · /resume | Pause or resume checking |
GET /v1/alerts | Alert inbox across all watches |
POST /v1/alerts/{id}/feedback | 👍/👎 that tunes the watch |
POST /v1/actions/{id}/approve | Approve a follow-up action |
GET /v1/explore | Community watches (trending, popular, new) |
POST /v1/watches/{id}/subscribe | Subscribe to a public watch |
Live updates
GET /v1/stream?watch={id} is a Server-Sent Events stream of your own events plus any watches you pass (up to 10): run.started, run.step, run.completed, alert.created, intake.updated and more. Reconnect with Last-Event-ID and missed events are replayed.
Webhooks
Add an HTTPS endpoint in Settings → Developer. Each alert is POSTed as JSON with an X-Watchtower-Signature: t=…,v1=… header. Verify it before trusting the body:
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts["t"])
if abs(time.time() - ts) > 300: # reject replays
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Use X-Watchtower-Delivery to de-duplicate retries. Endpoints that keep failing are disabled automatically and shown in settings.