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​