Skip to content

Errors and rate limits

Every /v1 error body is JSON and carries a machine-readable error field. Match on that field, never on the HTTP status alone — 403 has two distinct causes and they need different handling.

Status error What happened
401 api key unauthenticated The bearer token is absent, malformed, revoked, expired, or unknown. The API never says which.
402 quota_exceeded A plan quota was hit. metric names the exhausted quota; upgrade is always true.
403 forbidden The key’s bound role lacks the permission this route needs. Upgrading the plan will not help.
403 feature_gated The org’s plan does not include the API at all. feature names it; upgrade is true.
404 (a message) No such monitor in your org.
429 rate_limited The key exceeded its per-minute budget. retryAfter is seconds.

A monitor that belongs to another organisation answers 404, never 403. A foreign id and a missing one are deliberately indistinguishable, so the API never confirms that someone else’s monitor exists.

The limit is per API key, not per organisation and not per IP address. Two keys in the same org have two independent budgets; a single key shared across your fleet has one.

The cap is your plan’s api_rate_limit, measured over a one-minute window:

api_rate_limit Behaviour
0 The API is off. Every /v1 request answers 403 feature_gated, including GET /v1/me. This is the free tier — there is no rate limit to hit because there is no API.
a positive number That many requests per minute, per key. Exceeding it answers 429.
-1 Unlimited. No effective cap is applied.

A 429 carries a Retry-After header in seconds, and the same number as retryAfter in the body. Both are the time remaining in the current window. Wait that long and retry; do not retry immediately, and do not busy-poll.

Creating an API key on a plan whose api_rate_limit is 0 is refused the same way, from the dashboard — see Authentication.

A /v1 write can never buy you what the dashboard would refuse. POST /v1/monitors and PATCH /v1/monitors/{monitorId} run the same plan gates as the dashboard, in the same order, and answer with the same 402 quota_exceeded / 403 feature_gated bodies. Monitor count, minimum interval, probe locations and check types are all enforced on the API path.

/v1 is frozen. From the OpenAPI document’s own description:

The versioned public surface of WireWatch. v1 is FROZEN: an incompatible change gets a /v2. Responses carry monitor metadata only — config, assertions, config_version, paused_reason, org_id and deleted_at are deliberately not read back.

config and assertions are write-only on v1. From routes/v1/dto.ts, which defines the public monitor shape:

A v1 client can still SEND config/assertions on write (the write body uses the domain monitorInputSchema); it just cannot read them back yet. Adding a read-back view later is additive/non-breaking (no /v2 needed).

So: send them on POST and PATCH, and do not expect them in any response. New fields may appear in a response at any time — that is additive and not a breaking change, so parse responses leniently and ignore what you do not recognise. A change that would break a client gets a /v2 instead.