# API Reference Base URL: `http://localhost:8080/api/v1` Content-Type: `application/json` (required for POST/PUT) ## Authentication The API supports optional JWT authentication for user accounts. When authenticated, games are associated with the user account. ### Register User `POST /auth/register` Creates new user account and returns JWT token. **Request:** ```json { "username": "alice", "email": "alice@example.com", "password": "SecurePass123" } ``` - `username` (string, required): 1-40 characters, alphanumeric and underscore only - `email` (string, optional): Valid email address - `password` (string, required): Minimum 8 characters, must contain letter and number Accounts created here are identical to CLI-created accounts and do not expire. Returns 409 when the username or email is taken, and 503 when the registration limit (`-max-users`) or password-hashing capacity is exhausted. **Response (201):** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "userId": "550e8400-e29b-41d4-a716-446655440000", "username": "alice", "email": "alice@example.com", "expiresAt": "2025-01-14T10:30:00Z" } ``` ### Login `POST /auth/login` Authenticates user and returns JWT token. **Request:** ```json { "identifier": "alice", "password": "SecurePass123" } ``` - `identifier` (string, required): Username or email address - `password` (string, required): User password **Response (200):** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "userId": "550e8400-e29b-41d4-a716-446655440000", "username": "alice", "email": "alice@example.com", "expiresAt": "2025-01-14T10:30:00Z" } ``` ### Get Current User `GET /auth/me` Returns authenticated user information. Requires authentication. **Headers:** ``` Authorization: Bearer ``` **Response (200):** ```json { "userId": "550e8400-e29b-41d4-a716-446655440000", "username": "alice", "email": "alice@example.com", "createdAt": "2025-01-07T10:30:00Z" } ``` ## Game Endpoints ### Health Check `GET /health` Returns server and storage status. **Response (200):** ```json { "status": "healthy", "time": 1699123456, "storage": "ok" } ``` Storage states: - `"disabled"` - No database configured (`-dsn` or `CHESS_DSN`) - `"ok"` - Database operational with auth enabled - `"degraded"` - A persistence write failed or the write queue filled; live games continue in memory, but durable history is no longer complete The top-level `status` is also `"degraded"` when storage is degraded. ### Create Game `POST /games` Creates new game with specified players. Optional authentication associates game with user. **Headers (optional):** ``` Authorization: Bearer ``` **Request:** ```json { "white": { "type": 1, "level": 0, "searchTime": 0 }, "black": { "type": 2, "level": 15, "searchTime": 1000 }, "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" } ``` **Response (201):** ```json { "gameId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "turn": "w", "state": "ongoing", "moves": [], "players": { "white": {"id": "550e8400-...", "color": 1, "type": 1}, "black": {"id": "ai-player-...", "color": 2, "type": 2, "level": 15, "searchTime": 1000} } } ``` Note: When authenticated, human player IDs match the user's ID. Anonymous players receive unique UUIDs. ### Get Game `GET /games/{gameId}` Returns current game state. **Long-polling support:** Add query parameters for real-time updates: - `wait=true` - Enable long-polling (waits up to 30 seconds) - `moveCount=N` - Last known move count Returns immediately if game state changed, otherwise waits for updates: ``` GET /games/{gameId}?wait=true&moveCount=5 ``` Response includes all game data. Compare `moves` array length to detect changes. **Timeout behavior:** - Returns current state after 30 seconds even if no changes - Client disconnection cancels wait immediately - Game deletion notifies all waiting clients ### Get Durable Game History `GET /games/{gameId}/history` Returns the persisted replay line even after the live game has been unloaded from memory or the server has restarted. Games without a registered player are deleted 24 hours after their last activity; their history then returns 404. History is public to anyone who knows the game ID, matching the existing public live-game read model. Persistent storage must be enabled. The response contains the initial FEN and an ordered FEN after every move, so a client can replay the game without running a chess engine. **Response (200):** ```json { "gameId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "initialFen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "result": "white_wins", "startTimeUtc": "2026-09-07T12:00:00Z", "endTimeUtc": "2026-09-07T12:15:00Z", "players": { "white": {"id": "user-id", "color": 1, "type": 1, "claimedBy": "user-id", "name": "alice"}, "black": {"id": "player-id", "color": 2, "type": 1} }, "moves": [ { "moveNumber": 1, "moveUci": "e2e4", "fenAfterMove": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1", "playerColor": "w", "moveTimeUtc": "2026-09-07T12:00:05Z" } ] } ``` `result` is omitted while a game is ongoing. Persisted terminal values are `white_wins`, `black_wins`, `draw`, and `stalemate`. A claimed player's `name` is their username when the claim was recorded; it survives later renames and account deletion, and is omitted for anonymous and computer players. Returns 400 for a non-canonical game ID, 404 when the game has no durable record, and 503 when persistence is disabled or degraded. ### List My Stored Games `GET /users/me/games?limit=50&offset=0` Returns games associated with the authenticated user at creation time or by a later first-move slot claim. Requires `Authorization: Bearer ` and persistent storage. - `limit`: 1-100; defaults to 50 - `offset`: 0-1,000,000; defaults to 0 Each item contains game ID, initial FEN, final FEN (after the last stored move, or the initial FEN when there is none), result/timestamps, players, and move count. `nextOffset` is present only when another page exists. A game belongs to a user who created it while authenticated or claimed a slot with a first move; the association survives later player reconfiguration. **Response (200):** ```json { "games": [ { "gameId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "initialFen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "finalFen": "r1bqkb1r/pppp1Qpp/2n2n2/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4", "result": "white_wins", "startTimeUtc": "2026-09-07T12:00:00Z", "endTimeUtc": "2026-09-07T12:15:00Z", "moveCount": 7, "players": { "white": {"id": "user-id", "color": 1, "type": 1, "claimedBy": "user-id", "name": "alice"}, "black": {"id": "player-id", "color": 2, "type": 1} } } ], "limit": 50, "offset": 0 } ``` ### Make Move `POST /games/{gameId}/moves` Submits human move or triggers computer move. **Human move:** ```json {"move": "e2e4"} ``` **Computer move trigger:** ```json {"move": "cccc"} ``` ### Undo Moves `POST /games/{gameId}/undo` Reverts moves from history. ### Configure Players `PUT /games/{gameId}/players` Changes player configuration mid-game. ### Get Board `GET /games/{gameId}/board` Returns ASCII board visualization. ### Delete Game `DELETE /games/{gameId}` Unloads the live game from memory. Its persisted game and move history remain available through the history endpoint. Returns 204 on success. ## Error Format ```json { "error": "Description", "code": "ERROR_CODE", "details": "Additional context" } ``` Error codes: - `GAME_NOT_FOUND` - Invalid game ID - `INVALID_MOVE` - Illegal chess move - `NOT_HUMAN_TURN` - Wrong player type for turn - `GAME_OVER` - Game already ended - `GAME_CONFLICT` - Game changed while a move was being validated; refresh and retry - `STORAGE_UNAVAILABLE` - Durable history/list storage is disabled or degraded - `RATE_LIMIT_EXCEEDED` - Request limit exceeded - `INVALID_REQUEST` - Malformed request - `INVALID_CONTENT_TYPE` - Missing/wrong Content-Type header - `INVALID_FEN` - Invalid FEN format - `INTERNAL_ERROR` - Server error ## Rate Limiting - Standard: 10 requests/second/IP (general endpoints) - Development (`-dev`): 20 requests/second/IP - Registration: 5 requests/minute/IP - Login: 10 requests/minute/IP Exceeding limit returns 429 status. Behind a reverse proxy the client IP is the proxy's `X-Real-IP` header, trusted only from addresses listed in `-trusted-proxies`. Password hashing is bounded to four concurrent operations. When all are busy for five seconds, registration and login return 503 with `RESOURCE_LIMIT`. ## JWT Token Format Tokens are HS256-signed JWTs valid for 7 days. Include in Authorization header: ``` Authorization: Bearer ``` The scheme is case-insensitive and the token must use RFC 6750 syntax; a malformed header returns 401 even on endpoints where authentication is optional. Claims are `iss` (`chess-server`), `aud` (`chess-api`), `sub` (user ID), `iat`, `nbf`, `exp`, and `extra.session_id`. Tokens carry no profile data; use `/auth/me`. Authentication requires the session to exist, be unexpired, and belong to the JWT subject. Logging in again replaces the previous session.