Files
chess/doc/api.md
T

349 lines
9.2 KiB
Markdown

# 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 <token>
```
**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 <token>
```
**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 <token>` 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 <token>
```
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.