Base URL: https://<host>/api
All endpoints return JSON. Authenticated endpoints require:
Authorization: Bearer <access_token>
Create a new account.
Body
{ "username": "alice", "email": "alice@example.com", "password": "s3cr3t!" }Response 201
{
"accessToken": "<jwt>",
"refreshToken": "<opaque>",
"user": { "id": "<uuid>", "username": "alice", "email": "alice@example.com" }
}Body
{ "email": "alice@example.com", "password": "s3cr3t!" }Response 200: same shape as register.
Rotate token pair. Send current refresh token.
Body
{ "refreshToken": "<opaque>" }Response 200: new accessToken + refreshToken.
Revoke current refresh token.
Response 204: no body.
Search users by username prefix.
Response 200
[{ "id": "...", "username": "alice", "avatarUrl": null }]Public profile.
Response 200
{
"id": "...",
"username": "alice",
"avatarUrl": null,
"bio": null,
"country": "BR",
"createdAt": "2026-07-01T00:00:00Z",
"ratings": {
"bullet": { "rating": 1200, "gamesPlayed": 0 },
"blitz": { "rating": 1350, "gamesPlayed": 42 },
"rapid": { "rating": 1400, "gamesPlayed": 18 },
"classical": { "rating": 1200, "gamesPlayed": 0 }
}
}Update own profile.
Body (all fields optional)
{ "bio": "Chess enthusiast", "avatarUrl": "https://...", "country": "BR" }Response 200: updated user object.
Paginated game history for the authenticated user.
Query params: page=1&limit=20&timeControl=blitz
Response 200
{
"games": [
{
"id": "...",
"white": { "id": "...", "username": "alice" },
"black": { "id": "...", "username": "bob" },
"timeControl": "blitz",
"result": "white",
"termination": "checkmate",
"whiteRatingDelta": 12,
"blackRatingDelta": -12,
"startedAt": "2026-07-01T10:00:00Z",
"endedAt": "2026-07-01T10:05:30Z"
}
],
"total": 42,
"page": 1,
"limit": 20
}Same shape as above, for any user ID.
Full game detail including moves.
Response 200
{
"id": "...",
"white": { "id": "...", "username": "alice" },
"black": { "id": "...", "username": "bob" },
"timeControl": "blitz",
"initialTimeMs": 180000,
"incrementMs": 2000,
"status": "ended",
"result": "white",
"termination": "checkmate",
"pgn": "1. e4 e5 2. ...",
"isBotGame": false,
"startedAt": "...",
"endedAt": "...",
"moves": [
{
"moveNumber": 1, "color": "white",
"san": "e4", "uci": "e2e4",
"fenAfter": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
"timeLeftMs": 178000
}
]
}Start a game against Stockfish immediately.
Body
{ "timeControl": "blitz", "initialTimeMs": 180000, "incrementMs": 2000, "difficulty": "medium" }difficulty: easy | medium | hard | max
Response 201
{ "gameId": "<uuid>" }Request Stockfish analysis for a completed game.
Body
{ "gameId": "<uuid>" }Response 201
{ "id": "<uuid>", "status": "pending" }Fetch analysis results.
Response 200
{
"id": "...",
"gameId": "...",
"status": "completed",
"depth": 20,
"moveAnalyses": [
{
"moveNumber": 1,
"color": "white",
"evalCentipawns": 30,
"mateIn": null,
"bestMoveUci": "e2e4",
"classification": "best"
}
]
}status: pending | processing | completed | failed
List accepted friends.
Response 200
[{ "id": "...", "username": "bob", "avatarUrl": null, "isOnline": true }]Incoming pending friend requests.
Send a friend request.
Body
{ "username": "bob" }Response 201: friendship record.
Accept a pending request.
Response 200: updated friendship.
Remove friend or cancel request.
Response 204
Create an open invite (generates a shareable token).
Body
{
"timeControl": "rapid",
"initialTimeMs": 600000,
"incrementMs": 0,
"colorPreference": "random"
}Response 201
{ "token": "<64-char-token>", "expiresAt": "..." }Get invite details (public, no auth required).
Accept and start the game.
Response 200
{ "gameId": "<uuid>" }timeControl: bullet | blitz | rapid | classical
Query: limit=50
Response 200
[
{ "rank": 1, "username": "alice", "rating": 2100, "gamesPlayed": 300 }
]List latest 50 notifications.
Response 200
[
{
"id": "...",
"type": "friend_request",
"payload": { "fromUsername": "bob" },
"read": false,
"createdAt": "..."
}
]Mark all as read. Response 204
Mark one as read. Response 200
Connect to / namespace with handshake auth:
io('https://<host>', { auth: { token: '<access_token>' } })| Event | Payload | Description |
|---|---|---|
queue:join |
{ timeControl, initialTimeMs, incrementMs } |
Join matchmaking queue |
queue:leave |
{} |
Leave queue |
game:spectate |
{ gameId } |
Join game room (player or spectator) |
game:leave |
{ gameId } |
Leave game room |
game:move |
{ gameId, from, to, promotion? } |
Submit a move |
game:resign |
{ gameId } |
Resign |
game:draw:offer |
{ gameId } |
Offer a draw |
game:draw:accept |
{ gameId } |
Accept draw offer |
game:draw:decline |
{ gameId } |
Decline draw offer |
user:heartbeat |
{} |
Keep online status alive (every 30s) |
| Event | Payload | Description |
|---|---|---|
queue:matched |
{ gameId } |
Match found, navigate to game |
game:start |
{ gameId, white, black, timeControl, ... } |
Game started |
game:move:broadcast |
{ move, fen, clock } |
Opponent played a move |
game:move:rejected |
{ reason } |
Server rejected your move |
game:clock |
{ white, black, activeColor, lastUpdatedAt } |
Clock sync |
game:over |
{ result, termination, winner, whiteRatingDelta, blackRatingDelta } |
Game ended |
game:draw:offered |
{ byColor } |
Opponent offered a draw |
game:draw:declined |
{} |
Draw offer declined |
All HTTP errors use:
{ "statusCode": 400, "message": "Validation failed", "error": "Bad Request" }