Errors

The error response envelope, HTTP status codes, and the codes an integrator will actually see.

Every error is a JSON body with an error object and a meta object. Verified live against a running backend:

$ curl <UPZERO_API_URL>/api/v1/me/monitors
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Not authenticated",
    "details": {}
  },
  "meta": {
    "request_id": "9fed79f1-2083-4652-b28e-d81c36d5160d",
    "timestamp": "2026-09-16T12:52:09.096509+00:00"
  }
}

details is {} for most errors and an array of field-level entries for a validation failure:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human readable message",
    "details": [
      { "field": "url", "code": "INVALID_URL", "message": "URL must be a valid HTTP or HTTPS URL" }
    ]
  },
  "meta": { "request_id": "...", "timestamp": "..." }
}

Status codes

CodeWhen
400Malformed request (bad JSON, an id that isn't a UUID)
401Missing, invalid or expired credential
403Valid credential, but not authorized for this action or organization
404The resource does not exist, or the caller cannot see it (never distinguished, to avoid leaking that something exists)
409A conflicting or duplicate resource, or an invalid state transition
422The request was understood but failed validation
429Rate limited, see Rate limits
500 / 502 / 503A server or upstream failure, not the caller's fault

Two authentication cases that look alike return different codes: a request with no credential at all is 403 ("Not authenticated"), while a request whose credential was checked and rejected is 401. Both were confirmed against a running backend rather than assumed from the status table alone.

Common codes

The ones an integrator hits most:

CodeHTTPMeaning
UNAUTHORIZED401No token, or a token that failed verification
TOKEN_EXPIRED401The token expired
FORBIDDEN403No credential was presented at all
ORGANIZATION_ACCESS_DENIED403Authenticated, but not a member of the target organization
NOT_FOUND404Generic resource-not-found
VALIDATION_ERROR422Generic validation failure, see details for which field
LIMIT_EXCEEDED403A plan limit was reached (monitors, team members, and so on)
RATE_LIMITED / RATE_LIMITED_ORGANIZATION / RATE_LIMITED_USER429See Rate limits for which applies and why the three are not interchangeable

Codes tied to workflows are left out of this table on purpose: workflows are not part of this reference (see Endpoints).

Rate-limit responses are shaped differently

API_STANDARDS.md's example shows a 429 wrapped in the same error envelope as everything else. The running middleware does not do that: a 429 body is

{ "detail": "Rate limit exceeded. Please slow down.", "retry_after": 30, "code": "RATE_LIMITED" }

with no error/meta wrapper, verified against app/middleware/rate_limiting.py rather than against a live 429 (rate limiting is off in this environment by default, see Rate limits). Treat a 429's body shape as this one, not the general envelope above.

On this page