Skip to content

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 on message.
  • 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

HTTPWhen
400The request shape is wrong (bad URL, malformed JSON, oversized body).
401The token is missing, invalid, revoked, or expired.
403The token is fine but the request is not allowed (missing scope, IP not allowed).
404The resource doesn't exist (case id, ticket id, etc.).
409Idempotency-Key reused with a different body.
422Validation failed (per-field; error.errors is keyed by field name).
429Rate-limited. Honour Retry-After.
502Discord refused an action we relayed on your behalf (e.g. the bot lacks channel permissions). Fix the Discord-side cause before retrying.
5xxWe broke. Retry with backoff; we're alerted automatically.

Code catalogue

Authentication

error.codeStatusNotes
missing_token401No Authorization: Bearer … header.
invalid_token401Token doesn't exist, is revoked, or expired. No distinction between these on purpose — don't leak existence to a probing attacker.
https_required400Production endpoint reached over plain HTTP.
not_authenticated401The scope middleware ran without an upstream token bind. Should be unreachable in production.

Authorisation

error.codeStatusNotes
insufficient_scope403Token lacks the required scope. Carries error.required_scope.
ip_not_allowed403Request came from outside the token's IP allowlist.

Rate limiting

error.codeStatusNotes
rate_limited429Hit the per-token bucket. Carries error.bucket (read or write) and Retry-After.

Idempotency

error.codeStatusNotes
idempotency_key_reused409Same Idempotency-Key used with a different request body.
idempotency_key_too_long400Keys must be ≤ 128 characters.

Validation

error.codeStatusNotes
validation_failed422Per-field validation errors. error.errors is keyed by field name with the messages as arrays.
invalid_user_id400A user id path/query parameter is not a Discord snowflake (17–20 digits).
balance_would_be_negative422/economy/adjust would push the balance below zero.
xp_would_be_negative422/levelling/adjust delta would push total XP below zero.

Resource state

These come back when the resource exists but is in the wrong state for the requested write.

error.codeStatusNotes
case_deleted422PATCH /cases/{id} on a case whose status is deleted — deleted cases are read-only.
ticket_not_open422POST /tickets/{id}/message (non-internal) or /close on a ticket that isn't open.
ticket_deleted422Internal note on a deleted ticket.
no_ticket_channel422Non-internal ticket message, but the ticket has no Discord channel to deliver to.
close_reason_required422The guild's ticket settings require a close reason and none was sent.
giveaway_not_running422/giveaways/{id}/end on a giveaway that isn't active, paused, or scheduled.
giveaway_not_ended422/giveaways/{id}/reroll on a giveaway that isn't ended.
not_enough_entries422Reroll had no eligible entries left after exclusions.

Upstream (Discord)

error.codeStatusNotes
discord_send_failed502Discord rejected the message we tried to send for you — usually the bot is missing Send Messages in the ticket channel, or the channel was deleted.

Not found

error.codeStatusNotes
not_found404The resource id is not in this guild (or has been deleted).

Replay header

http
HTTP/1.1 200 OK
Idempotent-Replay: true

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

Phantom is a product of Hydra Labs. The bot is run as a managed service; you do not need to host it yourself.