Errors and status codes
The RFC 7807 problem-detail shape every /api/v1 error returns, the title tokens to branch on, and the status codes each endpoint can produce.
Every non-success response from /api/v1 is an RFC 7807 problem
detail, served with Content-Type: application/problem+json:
{
"type": "https://observer.example/problems/forbidden",
"title": "forbidden",
"status": 403,
"detail": "missing scope: write:incidents"
}
| Field | Notes |
|---|---|
type | URI ending in the same token as title. |
title | Stable machine-readable token. Branch on this. |
status | Repeats the HTTP status code. |
detail | Human-readable sentence. Present on most errors. Wording is not part of the contract and may change between releases. |
A 429 carries one extra field, retry_after, in seconds.
Match on title, not detail
detail exists to be read by a person looking at a failed request.
It interpolates runtime values and is reworded freely. Client logic
that switches on detail will break; logic that switches on
title will not.
Status codes
| Status | title values | When |
|---|---|---|
400 | invalid_json, invalid_body, invalid_title, invalid_severity, invalid_postmortem_url, invalid_type, invalid_description, invalid_note, invalid_status, invalid_window, missing_window, missing_param, invalid_range, invalid_resolution, range_too_large, invalid_request | The request body or query string is malformed, or a field failed validation. |
401 | unauthenticated | No bearer token, or the key is invalid or revoked. |
403 | forbidden | The key is valid but lacks the scope the endpoint requires. detail names the missing scope. |
404 | not_found, metric_not_found | The object does not exist, or it belongs to another organisation. |
409 | already_published, already_resolved, already_started, already_completed, already_canceled, canceled, not_started, not_manual_metric | The object is in a state that forbids the requested transition. |
413 | payload_too_large | POST /config/apply only: the document exceeds 1 MB. |
422 | config_invalid, invalid_change_event | The body parsed and was structurally valid, but its contents failed domain validation. |
429 | rate_limited | A rate limit was exceeded. See Rate limiting. |
500 | internal_error | Unhandled server error. Safe to retry. |
401, 403, 429 and 500 can come back from every endpoint.
The rest are per-endpoint; each operation page lists the ones it can
return.
On 404
A missing object and an object owned by another organisation return
the identical response. This is deliberate: a distinguishable 403
would let a caller enumerate which IDs exist in other tenants. Treat
404 as "not available to this key" rather than "does not exist".
Only endpoints addressing a specific object return 404. Collection
endpoints such as GET /services return 200 with an empty array
when there is nothing to list.
Success
Every successful call returns 200, including creates. The API
does not use 201, 202, or 204. DELETE /incidents/{id}
returns 200 with a body.
Rate limiting
Every response, success or error, carries:
X-RateLimit-Limit: <ceiling for the burst window>
X-RateLimit-Remaining: <calls left in the current burst window>
X-RateLimit-Reset: <burst window length, in seconds>
Limits apply per key across two windows: a short burst window and a
rolling daily cap. Whichever trips first produces the 429, and
detail says which one it was.
Ceilings vary by plan. Read them from the response headers rather
than hard-coding a number, and back off using retry_after from the
body rather than a fixed interval.