Errors
Every failure, on every route, uses one envelope:
{ "error": { "code": "invalid_request", "message": "seats_total must be 2..8" } }
Branch on code. It is stable, while message is written for humans and may be reworded
without notice.
The envelope covers the whole surface, including responses a framework would normally produce
itself: 404 on an unknown path and 405 on a wrong verb both come back in this shape.
The one exception is the WebSocket handshake, which is served by a different process and answers plain text.
The catalog
| Code | Status | Meaning | What to do |
|---|---|---|---|
unauthorized | 401 | Missing, malformed, unknown, revoked or expired key | Fix the credential. Do not retry |
forbidden | 403 | Missing scope, suspended tenant, not the creator, or tenant mismatch | Read message. Do not retry |
game_not_allowed | 403 | The game is not in this casino's allowlist | Have it allowed, then wait ~30 s and retry |
not_found | 404 | No such battle for this casino, unknown game, or unknown path | Do not retry |
invalid_request | 400 | Failed validation, malformed JSON, unknown field, or trailing content | Fix the request |
conflict | 409 | State conflict: already seated, no empty seat, battle not OPEN | Re-read state; do not blind-retry |
request_too_large | 413 | Body over 1 MiB | Fix the request |
unavailable | 503 | Still initialising, or at capacity | Retry with backoff |
internal | 500 / 502 | Server-side failure, or an upstream provider failure | Retry once, then alert |
method_not_allowed | 405 | Wrong verb on a real path | Fix the request |
Only unavailable is routinely retryable
503 unavailable means the instance is starting, or is at its in-flight battle limit.
Exponential backoff with jitter is the right response, and the request will likely succeed.
500/502 internal is worth one retry. A 502 on bot-seats specifically means an upstream
provider failed to mint a session, which is often transient.
Everything else is deterministic. Retrying a 400 produces another 400.
The ones that mislead
403 game_not_allowed right after the game was allowed. The catalog is cached, and a
change can take up to 30 seconds to reach the API. A change made in the console is immediate;
one made for you by your host may not be. Wait half a minute and retry before treating it as
an error. Verified live.
404 on a battle you are sure exists. Either it belongs to another casino, or your key
does. Cross-tenant reads answer 404 rather than 403, which makes this indistinguishable
from a typo. Check which key you sent before hunting for the battle.
400 unknown field "tenant_id". The tenant comes from the key. Remove the field.
409 player is already in an active battle. One player, one active battle, per casino.
Their previous battle has not reached a terminal state, and it may be waiting out its lobby
window rather than genuinely running.
500 failed to mint viewer token. Not a bug in your call. The instance has no viewer
signing key, so live viewing is unavailable while battles work normally. See
Viewer tokens.
A 405 you did not expect. Every path answers every verb with an explicit 405 plus an
Allow header naming what it does accept. Read the header:
HTTP/1.1 405 Method Not Allowed
Allow: GET
Validation messages
Validation failures return the same message the service itself would have produced, so they are specific and safe to log:
| Message | Cause |
|---|---|
seats_total must be 2..8 | Capacity out of range |
player_ref is required | Missing player_ref / creator_player_ref |
game_url is required for human players | Missing game_url on a seat |
unknown game_id | Missing or unknown game_id |
battle not found | Unknown id, or another casino's |
player is already in an active battle | The one-battle-per-player rule |
game not allowed for tenant | Allowlist |
tenant suspended | The casino is suspended |
tenant mismatch | Body tenant_id disagrees with the key |
Only one field's error is returned per request, in a fixed precedence, so fix them one at a time rather than expecting a full list.
Logging
Log code, the HTTP status, and the battle id. Log message too, but never key on it.
Never log the request body of a battle creation or seat call: it contains game_url /
creator_game_url, which are live provider session URLs. SlotBattle never lets them cross a
process boundary, and your logs should hold the same line.
Next
- Authentication: the
401/403cases in detail - Full reference with Try It