Errors
All error responses share the same shape:
json
{
"error": {
"code": "insufficient_scope",
"message": "Missing required scope: cases:write.",
"required_scope": "cases:write"
}
}The fields you can rely on:
error.code— a stable string. Match on this, not onmessage.error.message— human-readable; safe to surface to a logged-in admin but don't echo back to an end-user verbatim.- Some codes carry extra context fields (
required_scope,bucket,errors, etc.).
Status code mapping
| HTTP | When |
|---|---|
400 | The request shape is wrong (bad URL, malformed JSON, oversized body). |
401 | The token is missing, invalid, revoked, or expired. |
403 | The token is fine but the request is not allowed (missing scope, IP not allowed). |
404 | The resource doesn't exist (case id, ticket id, etc.). |
409 | Idempotency-Key reused with a different body. |
422 | Validation failed (per-field; error.errors is keyed by field name). |
429 | Rate-limited. Honour Retry-After. |
5xx | We broke. Retry with backoff; we're alerted automatically. |
Code catalogue
Authentication
error.code | Status | Notes |
|---|---|---|
missing_token | 401 | No Authorization: Bearer … header. |
invalid_token | 401 | Token doesn't exist, is revoked, or expired. No distinction between these on purpose — don't leak existence to a probing attacker. |
https_required | 400 | Production endpoint reached over plain HTTP. |
not_authenticated | 401 | The scope middleware ran without an upstream token bind. Should be unreachable in production. |
Authorisation
error.code | Status | Notes |
|---|---|---|
insufficient_scope | 403 | Token lacks the required scope. Carries error.required_scope. |
ip_not_allowed | 403 | Request came from outside the token's IP allowlist. |
Rate limiting
error.code | Status | Notes |
|---|---|---|
rate_limited | 429 | Hit the per-token bucket. Carries error.bucket (read or write) and Retry-After. |
Idempotency
error.code | Status | Notes |
|---|---|---|
idempotency_key_reused | 409 | Same Idempotency-Key used with a different request body. |
idempotency_key_too_long | 400 | Keys must be ≤ 128 characters. |
Validation
error.code | Status | Notes |
|---|---|---|
validation_failed | 422 | Per-field validation errors. error.errors is keyed by field name with the messages as arrays. |
invalid_user_id | 400 | A user id path/query parameter is not a Discord snowflake (17–20 digits). |
balance_would_be_negative | 422 | /economy/adjust would push the balance below zero. |
Not found
error.code | Status | Notes |
|---|---|---|
not_found | 404 | The resource id is not in this guild (or has been deleted). |
Replay header
http
HTTP/1.1 200 OK
Idempotent-Replay: trueA response carrying this header was served from the idempotency cache; the handler did not run again. Treat it as a successful "your previous call already did this" rather than a fresh execution.
