Errors
Error envelope
Section titled “Error envelope”The /api/v1/* surface uses a flat envelope:
{ "error": "url is required"}The error field is always a single human-readable string for these endpoints. There is no code, message, or nested object — clients should match on the HTTP status and (where useful) the literal error string.
There are two documented exceptions:
- The
/oauth2/*endpoints (/oauth2/register,/oauth2/token,/oauth2/revoke) follow RFC 7591 / RFC 6749 and return{"error":"<machine_code>", "error_description":"<message>"}(e.g.{"error":"invalid_client_metadata","error_description":"redirect_uris is required"}). - The monitor-test endpoint’s kill-switch 503 carries
{error, message}— see 503 Kill-switch below.
Common messages
Section titled “Common messages”A representative — not exhaustive — list of the literal error strings the API returns today, grouped by HTTP status. New endpoints add new strings; pin clients on the HTTP status first and only fall back to literal-string matching where the wording is part of an established contract (validator output, rate-limit hints).
400 Bad Request
Section titled “400 Bad Request”| Message | When |
|---|---|
name is required | Create monitor body missing name |
url is required | Create monitor body missing url |
interval_sec must be at least 60 | interval_sec below the 60-second minimum |
expected_status must be a single integer between 100 and 599 | expected_status non-integer or out of range |
type must be one of: down, flapping | Invalid incident type query value |
limit must be between 1 and 100 | Pagination limit out of range |
invalid monitor ID | Path parameter is non-numeric on monitor endpoints (e.g. /monitors/abc) |
invalid incident ID | Path parameter is non-numeric on /api/v1/incidents/{id} |
invalid key ID | Path parameter is non-numeric on key endpoints |
invalid request body | Body is not valid JSON |
label must be at least 3 characters | Key label below the minimum length |
label must be 100 characters or less | Key label above the maximum length |
missing code | OAuth callback missing code query parameter |
invalid or expired code | OAuth callback code rejected by GitHub |
url rejected: ip <addr> is in a blocked range | Target URL resolves to a blocked IP range (SSRF guard) |
401 Unauthorized
Section titled “401 Unauthorized”| Message | When |
|---|---|
missing authorization | No Authorization header |
invalid credentials | API key not recognised or revoked |
403 Forbidden
Section titled “403 Forbidden”| Message | When |
|---|---|
account suspended; contact support | Account suspended (e.g. by the port-scan heuristic). Returned on /api/v1/*. |
forbidden | Admin-only route hit by a non-admin caller. Opaque on purpose. |
404 Not Found
Section titled “404 Not Found”| Message | When |
|---|---|
monitor not found | Monitor ID is numeric but does not exist on this account |
incident not found | Incident ID is numeric but does not exist on this account |
key not found | API key ID does not exist on this account |
409 Conflict
Section titled “409 Conflict”| Message | When |
|---|---|
cannot delete last key | Tried to delete the only remaining API key |
key limit reached | Account already at the 10-key cap |
429 Too Many Requests
Section titled “429 Too Many Requests”Rate-limit responses carry a route-specific message in the flat envelope plus a Retry-After header (seconds). Examples:
| Limiter | Message |
|---|---|
Account-wide /api/v1/* cap | account rate limit exceeded; retry in <N>s |
| Monitor-creation cap | monitor creation rate limit exceeded; retry in <N>s |
| Per-monitor manual-test minimum interval | too many tests for this monitor; retry in <N>s |
| Per-domain cross-account test cap | domain rate limit exceeded; retry in <N>s |
Don’t pin clients on a single literal string; honour Retry-After and treat any 429 as transient.
500 Internal Server Error
Section titled “500 Internal Server Error”The body is {"error":"internal error"}. Retry with exponential backoff.
503 Kill-switch active (test-monitor endpoint only)
Section titled “503 Kill-switch active (test-monitor endpoint only)”POST /api/v1/monitors/{id}/test is the one endpoint where the 503 body deviates from the flat envelope. When the dispatcher’s global kill switch is engaged, the test endpoint returns:
{ "error": "kill_switch_active", "message": "check dispatches temporarily disabled"}with a Retry-After header. Both error and message are present. Other 503s (e.g. kill switch not configured on admin endpoints) keep the flat {"error":"..."} shape.
Common scenarios
Section titled “Common scenarios”SSRF blocked
Section titled “SSRF blocked”{ "error": "url rejected: ip 10.0.0.5 is in a blocked range"}Private IPs (10.x, 172.16-31.x, 192.168.x), loopback, and link-local addresses are blocked. See Security for details.
Validation failure
Section titled “Validation failure”{ "error": "expected_status must be a single integer between 100 and 599"}The body is the literal validator output — useful to surface back to the user.