Errors & codes
Every stable identifier in one place: the 16 socket error codes with handling advice, the close codes, HTTP statuses, and the result/termination enums.
Error shapes
jsonc
# Every REST error uses the envelope; errorCode mirrors the HTTP status:
{
"success": false,
"response": "error",
"responseCode": 409,
"data": null,
"error": { "errorCode": "#409", "errorMessage": "user is already in an active game" }
}
# Internal (coding) errors add error.trace for debugging.
# Every socket error is an error message with a stable code:
{ "type": "error", "room": "match_abc", "code": "illegal_move", "message": "illegal move" }
# room is omitted when the failure is session-scoped (queue, play_bot, subscribe …).
# Branch on code — message is human text and may change.Socket error codes
| Code | On | Meaning | Handling |
|---|---|---|---|
invalid_request | any | malformed message (unknown type, bad JSON shape) | client bug — log it; nothing to retry |
not_found | subscribe | no such room / no own game to re-attach to | route the user to the lobby |
not_active | game intents | the game is already finished | refresh from game_state; show the result |
not_started | move | the presence gate hasn't opened — status active but started:false | show the ready/waiting UI; send accept if your UX auto-confirms |
not_your_turn | move | it is the opponent's turn | snap the piece back |
invalid_move | move | the string is not UCI at all | client bug — validate before sending |
illegal_move | move | syntactically fine, illegal in the position (includes a missing promotion letter) | snap the piece back; check isPromotion() |
draw_too_soon | offer_draw | a move must pass between offers (rate limit) | disable the offer button until the next move |
no_draw_to_claim | claim_draw | threefold / 50-move is not actually on the board | only enable claim when your move list shows eligibility |
too_late_to_abort | abort | both sides have already moved | offer resign instead |
already_in_game | queue · play_bot | one active game per user (also: entered in an exclusive tournament) | subscribe() with no room to re-attach to the active game |
already_queued | queue | double enqueue | treat as success, or offer cancel |
rate_limited | queue | per-session enqueue rate limit (each enqueue costs a platform call) | back off; never loop queue/cancel |
bots_unavailable | play_bot | no Stockfish worker available | hide/disable bot play; retry later |
platform_unavailable | queue (rated) | the engine could not reach your /users/{userId} within ~2–3s | surface a retry; fix platform availability — there is deliberately no client-side fallback |
spectator_limit | subscribe | per-game spectator cap reached | tell the user the game is full |
Close codes
| Code | Meaning | Handling |
|---|---|---|
4001 | session token expired | STOP retrying with this token. Ask the platform for a fresh one (re-login / re-mint), then reconnect. The SDK emits authExpired and suppresses auto-reconnect. |
4002 | replaced by the user's own newer socket (second tab / device — newest wins) | Do NOT redial, or the two tabs kick each other forever. The SDK emits replaced and suppresses auto-reconnect. |
other | network drop, server restart, … | Auto-reconnect with the same token (the SDK does, with exponential backoff). Your own game is restored; re-subscribe spectates and re-queue on open. |
HTTP statuses (REST)
| Status | Meaning | Handling |
|---|---|---|
400 | malformed payload | fix the request shape |
401 / 403 | service auth failure — missing/wrong X-Service-Auth, or IP not whitelisted | fix credentials/network config; POST /v1/sessions additionally fails closed when the engine's secret is unconfigured |
404 | resource not found (match, tournament, puzzle) | check the id |
409 | conflict — user already in a game / already in a tournament / duplicate join | expected during normal operation; show the conflict to the user |
422 | validation — unknown variant, timeControl, or tournament format | offer only values from GET /v1/game-types |
503 | engine not ready, or Stockfish unavailable (review) | retry with backoff |
ℹ Callback direction (engine → your webhook): your responses drive the engine's retries — 2xx/409 delivered · permanent 4xx dead-letters · 5xx/timeout retries. See Callbacks & reliability.
Value enums
| Field | Values | Where |
|---|---|---|
result | white_win · black_win · draw · aborted | game_over (socket) — aborted only there; the game_result callback never fires for aborted games (match_aborted does) |
result (callback) | white_win · black_win · draw | game_result |
termination | checkmate · resign · timeout · stalemate · insufficient · threefold · fivefold · fifty_move · seventy_five · draw_agreement · abort | game_result — how the game ended |
reason (socket) | the termination values plus no_show | game_over |
match_aborted.reason | no_show · aborted | no_show = the gate was never confirmed (not a connection failure); aborted = pre-play abort intent |
gate | ready_check · grace_period · none | game_state — which presence gate applies |
fairplay_flag.flagType | repeat_pairing · sandbag_pattern · lopsided_pair · engine_assist | fairplay_flag callback |