Skip to content

Errors

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.

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).

MessageWhen
name is requiredCreate monitor body missing name
url is requiredCreate monitor body missing url
interval_sec must be at least 60interval_sec below the 60-second minimum
expected_status must be a single integer between 100 and 599expected_status non-integer or out of range
type must be one of: down, flappingInvalid incident type query value
limit must be between 1 and 100Pagination limit out of range
invalid monitor IDPath parameter is non-numeric on monitor endpoints (e.g. /monitors/abc)
invalid incident IDPath parameter is non-numeric on /api/v1/incidents/{id}
invalid key IDPath parameter is non-numeric on key endpoints
invalid request bodyBody is not valid JSON
label must be at least 3 charactersKey label below the minimum length
label must be 100 characters or lessKey label above the maximum length
missing codeOAuth callback missing code query parameter
invalid or expired codeOAuth callback code rejected by GitHub
url rejected: ip <addr> is in a blocked rangeTarget URL resolves to a blocked IP range (SSRF guard)
MessageWhen
missing authorizationNo Authorization header
invalid credentialsAPI key not recognised or revoked
MessageWhen
account suspended; contact supportAccount suspended (e.g. by the port-scan heuristic). Returned on /api/v1/*.
forbiddenAdmin-only route hit by a non-admin caller. Opaque on purpose.
MessageWhen
monitor not foundMonitor ID is numeric but does not exist on this account
incident not foundIncident ID is numeric but does not exist on this account
key not foundAPI key ID does not exist on this account
MessageWhen
cannot delete last keyTried to delete the only remaining API key
key limit reachedAccount already at the 10-key cap

Rate-limit responses carry a route-specific message in the flat envelope plus a Retry-After header (seconds). Examples:

LimiterMessage
Account-wide /api/v1/* capaccount rate limit exceeded; retry in <N>s
Monitor-creation capmonitor creation rate limit exceeded; retry in <N>s
Per-monitor manual-test minimum intervaltoo many tests for this monitor; retry in <N>s
Per-domain cross-account test capdomain 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.

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.

{
"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.

{
"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.