# API contract v1 All JSON is UTF-8 and uses `Content-Type: application/json`. API responses set `Cache-Control: no-store`. A request exceeding its route limit is rejected before parsing with `PAYLOAD_TOO_LARGE`; malformed JSON is `MALFORMED_JSON`. All numeric fields are decimal JSON integers, never strings. ## Common values | Type | Values / bound | | ------------------- | --------------------------------------------------------------------------- | | `role` | `player1`, `player2`, `spectator` | | `mode` | `human`, `bot` | | `phase` | `lobby`, `preparing`, `in_progress`, `finished`, `rematch_wait` | | `token` | exactly 32 lowercase hexadecimal characters | | `gameId`, `version` | unsigned 32-bit integer | | `x`, `y` | integer 0–9 | | `name` | 1–20 scalar values, at most 80 UTF-8 bytes | | `board` | exactly 100 ASCII cells: `0` unknown/water, `1` revealed ship, `2` miss, `3` hit, `4` sunk hit | Every state-changing request has `token` and `gameId`; they must precede any mutation validation. A stale `gameId` is rejected as `STALE_GAME`. ## Response envelope Success: ```json {"ok":true,"version":17,"gameId":4} ``` Failure (maximum 160 encoded bytes): ```json {"ok":false,"code":"NOT_YOUR_TURN","message":"Сейчас ход соперника","version":17} ``` `code` is one of `MALFORMED_JSON`, `PAYLOAD_TOO_LARGE`, `INVALID_NAME`, `INVALID_ROLE`, `INVALID_MODE`, `INVALID_COORDINATE`, `UNAUTHORIZED`, `NO_PLAYER_SLOT`, `NO_SPECTATOR_SLOT`, `FORBIDDEN_ROLE`, `WRONG_PHASE`, `NOT_YOUR_TURN`, `CELL_ALREADY_SHOT`, `STALE_GAME`, or `SERVER_BUSY`. `message` is Russian and at most 80 UTF-8 bytes. ## HTTP routes | Route | Maximum request | Response / maximum | | -------------------------- | --------------: | --------------------------------------------- | | `GET /api/info` | 128 B target | public device/slot state, 192 B | | `GET /api/health` | 128 B target | diagnostics without secrets, 320 B | | `POST /api/session/join` | 192 B body | `{name,requestedRole}`; token and role, 192 B | | `POST /api/session/resume` | 96 B body | `{token}`; role and state metadata, 192 B | | `POST /api/game/config` | 96 B body | `{token,gameId,mode}`; common envelope | | `POST /api/game/start` | 80 B body | `{token,gameId}`; common envelope | | `POST /api/game/shot` | 96 B body | `{token,gameId,x,y}`; common envelope | | `POST /api/game/rematch` | 80 B body | `{token,gameId}`; common envelope | | `POST /api/game/abort` | 80 B body | `{token,gameId}`; common envelope | | `GET /api/state?version=N` | 128 B target | one role-safe state, 512 B | The session token is in each POST body. For `GET /api/state`, it is supplied in `X-Session-Token`; absence creates a spectator-safe view. It is never a URL parameter. ## Role-safe state event The HTTP state response and WebSocket `state` event use this single 512-byte maximum schema: ```json {"type":"state","version":17,"gameId":4,"phase":"in_progress","mode":"human","viewer":"player1","turn":"player2","boards":["000...100 cells...","000...100 cells..."],"wins":[0,0],"winner":null,"statistics":[[3,2,1,1],[4,1,3,0]]} ``` `boards[0]` belongs to player 1 and `boards[1]` to player 2. The presenter replaces every unauthorized unhit ship with `0`. In `finished`, both boards may contain `1`. `winner` is `null` until `finished`, then player index `0` or `1`. Each compact statistics tuple is `[shots,hits,misses,shipsSunk]`. No other event contains a board. ## WebSocket Endpoint: `GET /ws`; all incoming frames are text JSON, at most 192 B. The first frame must arrive within 5 seconds: ```json {"type":"hello","token":"32-lowercase-hex-characters","version":17} ``` It receives a `state` snapshot. Supported inbound frames are `hello` (96 B), `ping` (16 B), `config` (96 B), `start` (80 B), `shot` (96 B), `rematch` (80 B), and `abort` (80 B). Their fields exactly match the corresponding HTTP commands. Outbound `state` is at most 512 B; `error` uses the common 160-byte failure envelope; `pong` is 16 B. Commands are idempotent when the same `gameId`, `version`, and command payload are retried: the server returns the current state rather than applying the action twice.