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.
Rate limiting
Section titled “Rate limiting”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.
Writes still obey your plan
Section titled “Writes still obey your plan”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.
The v1 contract
Section titled “The v1 contract”/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_idanddeleted_atare 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/assertionson write (the write body uses the domainmonitorInputSchema); it just cannot read them back yet. Adding a read-back view later is additive/non-breaking (no/v2needed).
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.