Skip to content

API overview

The API is a Fastify service you host yourself. There is no public endpoint — your base URL is wherever you deployed it.

https://<your-watcher-host>

Every route except a short public list requires a Bearer JWT.

  1. Create an account (once, from the CLI, for the first admin):

    Terminal window
    ADMIN_PASSWORD='a-long-random-password' pnpm users:create you@example.com --role admin --id local
  2. Exchange credentials for a token:

    Terminal window
    TOKEN=$(curl -s https://<your-host>/auth/login \
    -H 'content-type: application/json' \
    -d '{"email":"you@example.com","password":"a-long-random-password"}' \
    | jq -r .token)
  3. Send it on every request:

    Terminal window
    curl -s https://<your-host>/watches -H "authorization: Bearer $TOKEN"

Tokens are self-contained and last 7 days by default (AUTH_TOKEN_TTL). There is no refresh token: when a token expires, sign in again.

/health, /config/status, /auth/login, /auth/register, /auth/google, /auth/verify-email, /auth/forgot-password, /auth/reset-password.

Everything else returns 401 without a valid token.

Role Can do
user Everything scoped to their own data: watches, runs, feedback, trust rules, devices, AI assistance
admin The above, plus /admin/* — key pools and model configuration

A user calling an admin route gets 403.

Errors are JSON with an error message. Validation failures also include the underlying issues.

{ "error": "Invalid watch", "issues": [ { "path": ["threshold"], "message": "..." } ] }
Status Meaning
400 The body or query failed validation
401 Missing, malformed or expired token
403 Authenticated, but the role is insufficient
404 Not found, or not yours
409 Conflict — a duplicate watch id, or a run-all already in flight
422 A key was rejected by its live validation probe
429 Rate limited
502 An upstream model or search call failed
503 A capability is not configured on this instance

In-memory fixed windows, per process:

Scope Limit
/auth/* 20 requests per minute per IP
AI assistance routes 10 requests per minute per owner
Admin routes that probe a key 10 requests per minute overall

Exceeding one returns 429. Limits reset when the process restarts.

The API sends CORS headers only for origins listed in WEB_ORIGIN (comma-separated). With none configured it sends no CORS headers, so only same-origin and non-browser callers can reach it. There is no wildcard mode.

Send content-type: application/json when there is a body, and omit it when there is not — a bodyless POST declaring a JSON content type is rejected as an empty body.

Terminal window
curl -s https://<your-host>/health # {"status":"ok"}
curl -s https://<your-host>/config/status # booleans only

Both are public. /config/status reveals which integrations are configured (never their values), which is useful for support and worth knowing when you decide whether to expose the API publicly.