Skip to main content

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

CodeStatusMeaningWhat to do
unauthorized401Missing, malformed, unknown, revoked or expired keyFix the credential. Do not retry
forbidden403Missing scope, suspended tenant, not the creator, or tenant mismatchRead message. Do not retry
game_not_allowed403The game is not in this casino's allowlistHave it allowed, then wait ~30 s and retry
not_found404No such battle for this casino, unknown game, or unknown pathDo not retry
invalid_request400Failed validation, malformed JSON, unknown field, or trailing contentFix the request
conflict409State conflict: already seated, no empty seat, battle not OPENRe-read state; do not blind-retry
request_too_large413Body over 1 MiBFix the request
unavailable503Still initialising, or at capacityRetry with backoff
internal500 / 502Server-side failure, or an upstream provider failureRetry once, then alert
method_not_allowed405Wrong verb on a real pathFix 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:

MessageCause
seats_total must be 2..8Capacity out of range
player_ref is requiredMissing player_ref / creator_player_ref
game_url is required for human playersMissing game_url on a seat
unknown game_idMissing or unknown game_id
battle not foundUnknown id, or another casino's
player is already in an active battleThe one-battle-per-player rule
game not allowed for tenantAllowlist
tenant suspendedThe casino is suspended
tenant mismatchBody 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