Files
NowChessSystems/docs/Tournament-API.md
T
LQ63 3bda9466b5
Build & Test (NowChessSystems) TeamCity build failed
docs(tournament): spec
Added tournament spec to docs
2026-05-13 13:32:52 +02:00

5.9 KiB
Raw Blame History

NowChess Tournament API

Swiss-system bot tournaments. Bots are paired by score each round; all bots play every round (no eliminations). Game moves flow through the existing board and bot endpoints — the tournament module only orchestrates pairings, standings, and lifecycle.


Base path

/api/tournament

Routing: /api/tournamentnowchess-tournament-active:8086


Authentication

All endpoints require a valid JWT (Authorization: Bearer <token>).
Bot-facing streaming endpoints additionally require the token's subject to match the registered botId.


Data models

Tournament

{
  "id": "t7kXq2",
  "name": "Friday Night Bots",
  "status": "created | started | finished",
  "rounds": 5,
  "currentRound": 2,
  "timeControl": {
    "limitSeconds": 300,
    "incrementSeconds": 3
  },
  "createdBy": "userId",
  "createdAt": "2026-05-13T18:00:00Z",
  "startedAt": "2026-05-13T18:05:00Z",
  "finishedAt": null
}

Standing

{
  "rank": 1,
  "botId": "bot_abc",
  "botName": "StockfishClone",
  "points": 3.5,
  "wins": 3,
  "draws": 1,
  "losses": 0,
  "buchholz": 9.0
}

Tiebreaker: Buchholz score (sum of opponents' points).

Pairing

{
  "round": 2,
  "whiteBot": "bot_abc",
  "blackBot": "bot_xyz",
  "gameId": "j0nPtcjl",
  "result": "white | black | draw | ongoing"
}

TournamentEvent (SSE)

{ "type": "tournamentStarted", "tournamentId": "t7kXq2" }
{ "type": "roundStarted",      "tournamentId": "t7kXq2", "round": 2 }
{ "type": "pairingReady",      "tournamentId": "t7kXq2", "round": 2, "gameId": "j0nPtcjl", "color": "white" }
{ "type": "roundFinished",     "tournamentId": "t7kXq2", "round": 2 }
{ "type": "tournamentFinished","tournamentId": "t7kXq2" }

Endpoints

Tournament lifecycle

Create tournament

POST /api/tournament

Body:

{
  "name": "Friday Night Bots",
  "rounds": 5,
  "timeControl": {
    "limitSeconds": 300,
    "incrementSeconds": 3
  }
}

Response 201 Created:

{ "id": "t7kXq2" }

The creator becomes the tournament director. Only the director can start and delete the tournament.


Get tournament

GET /api/tournament/{tournamentId}

Response 200 OK: Tournament object.


List tournaments

GET /api/tournament

Query params:

Param Type Default
status created|started|finished (all)
limit integer (max 50) 20
offset integer 0

Response 200 OK:

{
  "tournaments": [ /* Tournament[] */ ],
  "total": 42
}

Start tournament

POST /api/tournament/{tournamentId}/start

Requires at least 2 registered bots. Computes round 1 pairings (random for round 1; score-based from round 2). Creates one game per pairing via POST /api/board/game.

Response 200 OK: updated Tournament object.


Delete tournament

DELETE /api/tournament/{tournamentId}

Only allowed while status == "created". Response 204 No Content.


Bot registration

Register bot

POST /api/tournament/{tournamentId}/bots

Registers a bot for the tournament. Must be called before the tournament starts.
The token subject must match the bot being registered.

Body:

{ "botId": "bot_abc" }

Response 200 OK:

{ "botId": "bot_abc", "tournamentId": "t7kXq2" }

Unregister bot

DELETE /api/tournament/{tournamentId}/bots/{botId}

Only allowed while status == "created". Response 204 No Content.


List registered bots

GET /api/tournament/{tournamentId}/bots

Response 200 OK:

{
  "bots": [
    { "botId": "bot_abc", "botName": "StockfishClone" }
  ]
}

Standings and pairings

Get standings

GET /api/tournament/{tournamentId}/standings

Response 200 OK:

{ "standings": [ /* Standing[] */ ] }

Get pairings for a round

GET /api/tournament/{tournamentId}/rounds/{round}/pairings

Response 200 OK:

{ "pairings": [ /* Pairing[] */ ] }

Bot streaming

Stream tournament events

GET /api/tournament/{tournamentId}/stream

Headers: Accept: text/event-stream

Server-Sent Events stream scoped to this tournament. The bot receives pairingReady events when it is assigned a game, at which point it should connect to the existing bot game stream:

GET /bot/stream/game/{gameId}    (existing endpoint)
POST /bot/game/{gameId}/move/{uci}  (existing endpoint)

The tournament module never sends moves — bots do that themselves through the existing bot endpoints.


Typical bot flow

1. POST /api/tournament                          # director creates tournament
2. POST /api/tournament/{id}/bots               # each bot registers
3. POST /api/tournament/{id}/start              # director starts
4. GET  /api/tournament/{id}/stream  (SSE)      # each bot opens stream

   -- per round --
5. receive: pairingReady { gameId, color }
6. GET  /bot/stream/game/{gameId}               # existing endpoint
7. POST /bot/game/{gameId}/move/{uci}           # existing endpoint, repeated
   -- game ends --

8. receive: roundFinished
9. GET  /api/tournament/{id}/standings          # optional, inspect scores
   -- repeat 59 for each round --

10. receive: tournamentFinished
11. GET /api/tournament/{id}/standings          # final ranking

Error responses

Status Meaning
400 Invalid request body or parameters
401 Missing or invalid JWT
403 Action not allowed (wrong director, wrong bot, etc.)
404 Tournament or bot not found
409 Tournament already started / bot already registered