Authentication
Every /v1 request is scoped to one organization (tenant) — you only ever
see your own organization’s sites and reports. Two credentials are accepted,
both resolving to an organization:
| Credential | Sent as | Who uses it |
|---|---|---|
| API key | x-api-key: wah_… header |
CMS integrations (Umbraco, Sanity, …) |
| Dashboard session | Authorization: Bearer <sessionToken> (or cookie) |
The Overwatch dashboard |
API keys
Section titled “API keys”An API key is minted for one organization; the scope is baked into the key and can’t be widened. Optionally, a key can be restricted to a subset of the organization’s sites — useful for an agency minting one key per client. A site-scoped key only sees its granted sites and can’t register new ones.
Getting a key
Section titled “Getting a key”API keys are managed from the Overwatch dashboard (or the session-only API keys endpoints):
- Sign in to the dashboard and select your organization.
- Open the API keys section and create a new key, optionally scoping it to specific sites.
- Copy the key value immediately — the plaintext is shown only once at creation. Store it as a secret in your CMS integration.
A key can be revoked (immediate and irreversible) or re-rolled (the secret rotates; name, site scope, and remaining lifetime carry over) at any time.
Making a request
Section titled “Making a request”Send the key in the x-api-key header on every request:
curl https://api.overwatch.weareheavy.dev/v1/sites \ -H "x-api-key: wah_your_api_key_here"A successful call returns JSON scoped to your organization:
{ "sites": [ { "id": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", "tenant_id": "7c9e6f0a-1b2c-3d4e-5f60-718293a4b5c6", "hostname": "example.com", "root_url": "https://example.com/", "cadence_minutes": 10080, "max_pages": 5000, "max_concurrency": 100, "enabled": 1, "last_run_at": 1750000000000, "next_run_at": 1750604800000, "created_at": 1749900000000, "scanner_enabled": 1, "a11y_enabled": 0, "a11y_cadence_minutes": null, "a11y_next_run_at": null, "performance_enabled": 1, "uptime_enabled": 1, "exclude_paths": "[]", "strip_params": "[]" } ], "quota": { "count": 1, "included": 5, "overageEnabled": false }}Dashboard sessions
Section titled “Dashboard sessions”The dashboard signs in through the auth layer (mounted at /api/auth/* —
email OTP, organizations, invitations). On sign-in the API returns the session
token in the set-auth-token response header; the dashboard replays it as
Authorization: Bearer <sessionToken>. The session is scoped to its active
organization.
Some endpoint groups are session-only — an API-key caller gets 403:
- API key management (
/v1/keys*) - Member management (
/v1/members*) - Organization-wide usage (
GET /v1/usage) - Billing (
/v1/billing) - Notification settings (
/v1/notifications*,/v1/sites/{id}/notifications) - Account quotas and limit requests (
/v1/me/*)
The rationale: a CMS plugin holding a key shouldn’t be able to mint more keys, manage people, reroute alerts, or read billing.
Roles and permissions
Section titled “Roles and permissions”Dashboard sessions carry an organization role:
- owner / admin — full management: sites, keys, members, usage.
- member — restricted: sees only an explicitly granted allowlist of sites
(default: none), and may trigger crawls only when granted
canCrawl.
API keys are not role-gated — a key acts with management capability within its (possibly site-scoped) access. Scope a key to specific sites when it shouldn’t touch the whole organization.
Errors
Section titled “Errors”If credentials are missing or invalid, the API responds with 401:
{ "error": "invalid api key" }A valid credential without permission for the action gets 403. All errors
follow the same shape — { "error": "<message>" } — with an appropriate HTTP
status code. See the API Reference for the responses of each endpoint.