f088c4e9ff
Reviewed-on: #35 Reviewed-by: Leon Hermann <lq@blackhole.local>
772 lines
23 KiB
YAML
772 lines
23 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: NowChess Board API
|
|
description: |
|
|
REST API for the NowChess application. Designed to feel familiar to users
|
|
of the [lichess API](https://lichess.org/api).
|
|
|
|
## Authentication
|
|
Most endpoints require a Bearer token:
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
Authentication is reserved for future implementation — endpoints are currently
|
|
open unless noted otherwise.
|
|
|
|
## Move notation
|
|
Moves are expressed in **UCI notation**: `{from}{to}[promotion]`
|
|
- Normal move: `e2e4`
|
|
- Capture: `d5e6`
|
|
- Promotion: `e7e8q` (q=queen, r=rook, b=bishop, n=knight)
|
|
- Castling: `e1g1` (kingside white), `e1c1` (queenside white)
|
|
|
|
## Streaming
|
|
Endpoints that support streaming return **NDJSON** (newline-delimited JSON).
|
|
Request them with:
|
|
```
|
|
Accept: application/x-ndjson
|
|
```
|
|
Each line of the response is a complete JSON object. Empty lines are
|
|
keep-alive heartbeats.
|
|
|
|
## Rate limiting
|
|
Requests that exceed the rate limit receive `429 Too Many Requests`.
|
|
Honour the `Retry-After` response header and wait before retrying.
|
|
version: 1.0.0
|
|
contact:
|
|
name: NowChess
|
|
license:
|
|
name: MIT
|
|
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: Local development server
|
|
|
|
tags:
|
|
- name: game
|
|
description: Create and manage chess games
|
|
- name: move
|
|
description: Make moves and navigate game history
|
|
- name: draw
|
|
description: Draw offers and claims
|
|
- name: import
|
|
description: Load a game from FEN or PGN
|
|
- name: export
|
|
description: Export a game as FEN or PGN
|
|
|
|
paths:
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Game lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/api/board/game:
|
|
post:
|
|
operationId: createGame
|
|
tags: [game]
|
|
summary: Create a new game
|
|
description: |
|
|
Creates a new chess game starting from the initial position.
|
|
Returns the full game state including the generated `gameId`.
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateGameRequest'
|
|
responses:
|
|
'201':
|
|
description: Game created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameFull'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}:
|
|
get:
|
|
operationId: getGame
|
|
tags: [game]
|
|
summary: Get game state
|
|
description: Returns the full current state of a game.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: Current game state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameFull'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/stream:
|
|
get:
|
|
operationId: streamGame
|
|
tags: [game]
|
|
summary: Stream game events
|
|
description: |
|
|
Opens a persistent NDJSON stream for a game. The first object sent is
|
|
a `gameFull` event containing the complete game state. Subsequent
|
|
objects are `gameState` events sent whenever the game changes (move
|
|
made, draw offered, game over, etc.).
|
|
|
|
Empty lines are heartbeats to keep the connection alive.
|
|
|
|
Connect with:
|
|
```
|
|
Accept: application/x-ndjson
|
|
```
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: NDJSON event stream
|
|
content:
|
|
application/x-ndjson:
|
|
schema:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/GameFullEvent'
|
|
- $ref: '#/components/schemas/GameStateEvent'
|
|
- $ref: '#/components/schemas/ErrorEvent'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/resign:
|
|
post:
|
|
operationId: resignGame
|
|
tags: [game]
|
|
summary: Resign the game
|
|
description: The active player resigns. The game ends immediately.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: Resignation accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OkResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Move-making
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/api/board/game/{gameId}/move/{uci}:
|
|
post:
|
|
operationId: makeMove
|
|
tags: [move]
|
|
summary: Make a move
|
|
description: |
|
|
Submit a move in UCI notation. The move must be legal for the side
|
|
currently to move.
|
|
|
|
For promotion moves include the target piece as the fifth character:
|
|
`e7e8q`, `a2a1r`, etc. Promotion moves without the fifth character
|
|
are rejected with `400 INVALID_MOVE`.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
- name: uci
|
|
in: path
|
|
required: true
|
|
description: Move in UCI notation (e.g. `e2e4`, `e7e8q`)
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-h][1-8][a-h][1-8][qrbn]?$'
|
|
example: e2e4
|
|
responses:
|
|
'200':
|
|
description: Move applied — returns updated game state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameState'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/moves:
|
|
get:
|
|
operationId: getLegalMoves
|
|
tags: [move]
|
|
summary: Get legal moves
|
|
description: |
|
|
Returns all legal moves for the side currently to move.
|
|
Optionally filter to moves originating from a single square.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
- name: square
|
|
in: query
|
|
required: false
|
|
description: Filter to moves from this square (e.g. `e2`)
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-h][1-8]$'
|
|
example: e2
|
|
responses:
|
|
'200':
|
|
description: List of legal moves
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LegalMovesResponse'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/undo:
|
|
post:
|
|
operationId: undoMove
|
|
tags: [move]
|
|
summary: Undo the last move
|
|
description: Reverts the most recent move. Returns the updated game state.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: Move undone
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameState'
|
|
'400':
|
|
description: No moves to undo
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/redo:
|
|
post:
|
|
operationId: redoMove
|
|
tags: [move]
|
|
summary: Redo a previously undone move
|
|
description: Re-applies the next move in the undo stack. Returns the updated game state.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: Move redone
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameState'
|
|
'400':
|
|
description: No moves to redo
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Draw handling
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/api/board/game/{gameId}/draw/{action}:
|
|
post:
|
|
operationId: drawAction
|
|
tags: [draw]
|
|
summary: Offer, accept, decline, or claim a draw
|
|
description: |
|
|
Perform a draw-related action:
|
|
|
|
| Action | Description |
|
|
|-----------|-------------|
|
|
| `offer` | Offer a draw to the opponent |
|
|
| `accept` | Accept the opponent's draw offer |
|
|
| `decline` | Decline the opponent's draw offer |
|
|
| `claim` | Claim a draw under the fifty-move rule (only valid when `status` is `fiftyMoveAvailable`) |
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
- name: action
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
enum: [offer, accept, decline, claim]
|
|
responses:
|
|
'200':
|
|
description: Action accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OkResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Import
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/api/board/game/import/fen:
|
|
post:
|
|
operationId: importFen
|
|
tags: [import]
|
|
summary: Load a position from FEN
|
|
description: |
|
|
Creates a new game from a FEN string. The game starts at the position
|
|
described by the FEN; move history prior to that position is not
|
|
available.
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ImportFenRequest'
|
|
responses:
|
|
'201':
|
|
description: Game created from FEN
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameFull'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/import/pgn:
|
|
post:
|
|
operationId: importPgn
|
|
tags: [import]
|
|
summary: Load a game from PGN
|
|
description: |
|
|
Creates a new game by replaying all moves in a PGN string. The game
|
|
starts at the position after the final move in the PGN; undo is
|
|
available for every replayed move.
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ImportPgnRequest'
|
|
responses:
|
|
'201':
|
|
description: Game created from PGN
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/GameFull'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Export
|
|
# ---------------------------------------------------------------------------
|
|
|
|
/api/board/game/{gameId}/export/fen:
|
|
get:
|
|
operationId: exportFen
|
|
tags: [export]
|
|
summary: Export current position as FEN
|
|
description: Returns the FEN string representing the current board position.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: FEN string
|
|
content:
|
|
text/plain:
|
|
schema:
|
|
type: string
|
|
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
/api/board/game/{gameId}/export/pgn:
|
|
get:
|
|
operationId: exportPgn
|
|
tags: [export]
|
|
summary: Export game as PGN
|
|
description: Returns the full PGN for the game including headers and move text.
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/gameId'
|
|
responses:
|
|
'200':
|
|
description: PGN text
|
|
content:
|
|
application/x-chess-pgn:
|
|
schema:
|
|
type: string
|
|
example: |
|
|
[Event "NowChess game"]
|
|
[White "Player1"]
|
|
[Black "Player2"]
|
|
[Result "*"]
|
|
|
|
1. e4 e5 2. Nf3 *
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/TooManyRequests'
|
|
|
|
# =============================================================================
|
|
# Components
|
|
# =============================================================================
|
|
|
|
components:
|
|
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
description: 'Personal access token — `Authorization: Bearer <token>`'
|
|
|
|
parameters:
|
|
gameId:
|
|
name: gameId
|
|
in: path
|
|
required: true
|
|
description: 8-character alphanumeric game ID (e.g. `Qa7FJNk2`)
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Za-z0-9]{8}$'
|
|
example: Qa7FJNk2
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Invalid input
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
Unauthorized:
|
|
description: Missing or invalid authentication token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
NotFound:
|
|
description: Game not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
TooManyRequests:
|
|
description: Rate limit exceeded — see `Retry-After` header
|
|
headers:
|
|
Retry-After:
|
|
description: Seconds to wait before retrying
|
|
schema:
|
|
type: integer
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ApiError'
|
|
|
|
schemas:
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Requests
|
|
# -------------------------------------------------------------------------
|
|
|
|
CreateGameRequest:
|
|
type: object
|
|
description: Parameters for creating a new game. All fields are optional.
|
|
properties:
|
|
white:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
black:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
|
|
ImportFenRequest:
|
|
type: object
|
|
required: [fen]
|
|
properties:
|
|
fen:
|
|
type: string
|
|
description: Complete FEN string (6 fields)
|
|
example: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
|
white:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
black:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
|
|
ImportPgnRequest:
|
|
type: object
|
|
required: [pgn]
|
|
properties:
|
|
pgn:
|
|
type: string
|
|
description: PGN text (headers and move list)
|
|
example: "1. e4 e5 2. Nf3 Nc6 *"
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Game state
|
|
# -------------------------------------------------------------------------
|
|
|
|
GameFull:
|
|
type: object
|
|
description: Complete game information including players and current state.
|
|
required: [gameId, white, black, state]
|
|
properties:
|
|
gameId:
|
|
type: string
|
|
description: Unique 8-character game identifier
|
|
example: Qa7FJNk2
|
|
white:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
black:
|
|
$ref: '#/components/schemas/PlayerInfo'
|
|
state:
|
|
$ref: '#/components/schemas/GameState'
|
|
|
|
GameState:
|
|
type: object
|
|
description: |
|
|
The current game state. Included in `GameFull` and returned by move
|
|
endpoints and stream events.
|
|
required: [fen, pgn, turn, status, moves, undoAvailable, redoAvailable]
|
|
properties:
|
|
fen:
|
|
type: string
|
|
description: FEN string for the current position
|
|
example: rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1
|
|
pgn:
|
|
type: string
|
|
description: PGN move text for the full game so far
|
|
example: "1. e4"
|
|
turn:
|
|
type: string
|
|
enum: [white, black]
|
|
description: The side to move
|
|
status:
|
|
$ref: '#/components/schemas/GameStatus'
|
|
winner:
|
|
type: string
|
|
enum: [white, black]
|
|
description: Set when `status` is `checkmate` or `resign`
|
|
nullable: true
|
|
moves:
|
|
type: array
|
|
description: All moves played so far, in UCI notation
|
|
items:
|
|
type: string
|
|
example: [e2e4, e7e5, g1f3]
|
|
undoAvailable:
|
|
type: boolean
|
|
description: Whether `POST /undo` is currently valid
|
|
redoAvailable:
|
|
type: boolean
|
|
description: Whether `POST /redo` is currently valid
|
|
|
|
GameStatus:
|
|
type: string
|
|
description: |
|
|
Current game status:
|
|
|
|
| Value | Meaning |
|
|
|-------|---------|
|
|
| `started` | Game in progress, no special condition |
|
|
| `check` | Side to move is in check |
|
|
| `checkmate` | Side to move is checkmated — game over |
|
|
| `stalemate` | Side to move has no legal moves, not in check — game over (draw) |
|
|
| `resign` | A player resigned — game over |
|
|
| `draw` | Draw agreed or claimed — game over |
|
|
| `drawOffered` | Waiting for the opponent to accept or decline a draw offer |
|
|
| `fiftyMoveAvailable` | Fifty-move rule threshold reached; active player may claim draw |
|
|
| `insufficientMaterial` | Neither side has enough pieces to deliver checkmate — game over (draw) |
|
|
enum:
|
|
- started
|
|
- check
|
|
- checkmate
|
|
- stalemate
|
|
- resign
|
|
- draw
|
|
- drawOffered
|
|
- fiftyMoveAvailable
|
|
- insufficientMaterial
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Moves
|
|
# -------------------------------------------------------------------------
|
|
|
|
LegalMovesResponse:
|
|
type: object
|
|
required: [moves]
|
|
properties:
|
|
moves:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/LegalMove'
|
|
|
|
LegalMove:
|
|
type: object
|
|
required: [from, to, uci, moveType]
|
|
properties:
|
|
from:
|
|
type: string
|
|
description: Origin square in algebraic notation
|
|
example: e2
|
|
to:
|
|
type: string
|
|
description: Destination square in algebraic notation
|
|
example: e4
|
|
uci:
|
|
type: string
|
|
description: Full move in UCI notation
|
|
example: e2e4
|
|
moveType:
|
|
$ref: '#/components/schemas/MoveType'
|
|
promotion:
|
|
type: string
|
|
enum: [queen, rook, bishop, knight]
|
|
description: Target piece for promotion moves
|
|
nullable: true
|
|
|
|
MoveType:
|
|
type: string
|
|
description: Classification of the move
|
|
enum:
|
|
- normal
|
|
- capture
|
|
- castleKingside
|
|
- castleQueenside
|
|
- enPassant
|
|
- promotion
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Streaming events
|
|
# -------------------------------------------------------------------------
|
|
|
|
GameFullEvent:
|
|
type: object
|
|
description: |
|
|
First event on a game stream. Contains the complete game snapshot.
|
|
required: [type, game]
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum: [gameFull]
|
|
game:
|
|
$ref: '#/components/schemas/GameFull'
|
|
|
|
GameStateEvent:
|
|
type: object
|
|
description: |
|
|
Emitted on a game stream whenever the game state changes (move played,
|
|
draw offered, game over, etc.).
|
|
required: [type, state]
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum: [gameState]
|
|
state:
|
|
$ref: '#/components/schemas/GameState'
|
|
|
|
ErrorEvent:
|
|
type: object
|
|
description: Emitted on a game stream when an error occurs.
|
|
required: [type, error]
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum: [error]
|
|
error:
|
|
$ref: '#/components/schemas/ApiError'
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Shared types
|
|
# -------------------------------------------------------------------------
|
|
|
|
PlayerInfo:
|
|
type: object
|
|
required: [id, displayName]
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Unique player identifier
|
|
example: player1
|
|
displayName:
|
|
type: string
|
|
description: Human-readable display name
|
|
example: Alice
|
|
|
|
OkResponse:
|
|
type: object
|
|
required: [ok]
|
|
properties:
|
|
ok:
|
|
type: boolean
|
|
enum: [true]
|
|
|
|
ApiError:
|
|
type: object
|
|
required: [code, message]
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: Machine-readable error code
|
|
example: INVALID_MOVE
|
|
message:
|
|
type: string
|
|
description: Human-readable error description
|
|
example: e2e5 is not a legal move
|
|
field:
|
|
type: string
|
|
description: Request field that caused the error, if applicable
|
|
example: uci
|
|
nullable: true
|