Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antonelli-Tech-Solutions/spades/llms.txt

Use this file to discover all available pages before exploring further.

The player who creates a table is its host. Host status grants exclusive access to a set of management endpoints that let the host shape the composition and settings of the table before and during a game. All host-only endpoints verify that the caller is the current host and return 403 otherwise. Every endpoint also requires the standard x-session-id and x-player-id authentication headers.

POST /api/tables/:tableId/kick

Removes a seated player or observer from the table. The host cannot kick themselves. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the host) Request body
{ "playerId": "<targetPlayerId>" }
Behaviour by table state
StateResult
waitingTarget’s seat is vacated (set to null).
playing (seated target)Target’s seat is immediately filled by a bot (bot:<seat>). If no human players remain after the substitution, the table is terminated.
playing (observer target)Observer is removed; game continues unaffected.
WebSocket events
  • PLAYER_KICKED — broadcast to the entire table room with { playerId, seat }. seat is null if the target was an observer.
  • KICKED_FROM_TABLE — sent to the kicked player’s personal notification channel (player:{playerId}:notify) with { tableId }.
Responses
StatusMeaning
200Player kicked. Body: updated table state, or { message } if the table was terminated as a result.
400Missing playerId in body, host attempted to kick themselves, or target player is not at the table
401Missing or invalid session
403Caller is not the table host
404Table not found

POST /api/tables/:tableId/transfer-host

Transfers host privileges to another seated human player. Works in both waiting and playing states. The target must be seated at the table and must not be a bot. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the current host) Request body
{ "playerId": "<target-player-uuid>" }
WebSocket event: HOST_CHANGED is broadcast to the table room with { newHostPlayerId, newHostSeat }. Responses
200
object
Host transferred successfully.
StatusMeaning
400Target player is not seated at the table, or target is a bot
401Missing or invalid session
403Caller is not the current host
404Table not found
409Concurrent modification — retry the request

POST /api/tables/:tableId/assign-seat

Moves a seated player to a different empty seat. Only allowed while the table is in waiting status (game not yet started). Uses optimistic locking internally to prevent a race with markTablePlaying; if the table transitions to playing between the read and the write the request returns 503 — retry. If the target player is already in the requested seat, the call succeeds as a no-op (200) without emitting any WebSocket events. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the host) Request body
{ "playerId": "<player-uuid>", "seat": "south" }
Valid seat values: north, east, south, west. Both fields are required. Responses
200
object
Seat assigned.
tableId
string
UUID of the table.
seat
string
The seat the player was moved to.
StatusMeaning
400Missing playerId or seat, or invalid seat value
401Missing or invalid session
403Caller is not the table host
404Table not found
409Game is in progress, target player is not seated at the table, or the target seat is already occupied
503Concurrent modification — retry the request

POST /api/tables/:tableId/visibility

Changes the table’s visibility setting. The joinPolicy is automatically adjusted to remain compatible with the new visibility (e.g. switching to private forces invite-only). If the requested visibility matches the current value, the request succeeds as a no-op without firing any events. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the host) Request body
{ "visibility": "friends-only" }
Valid values: "public", "friends-only", "private". WebSocket events A TABLE_VISIBILITY_CHANGED event is always broadcast to the table room (when visibility actually changes) so seated players and observers are informed of the change. Payload: { tableId, visibility, oldVisibility, joinPolicy }. Lobby and friend-list side effects The server fires additional WebSocket events whenever the visibility changes so that lobby and friends-list UIs update in real time.
TransitionEvents fired
Leaving publicTABLE_REMOVED broadcast to the lobby channel
Entering publicTABLE_CREATED broadcast to the lobby channel
Leaving friends-onlyTABLE_REMOVED sent to each of the host’s friends via their personal notification channels
Entering friends-onlyTABLE_CREATED sent to each of the host’s friends via their personal notification channels
Involving privateNo lobby or friend-list events in either direction
Responses
200
object
Visibility changed.
tableId
string
UUID of the table.
visibility
string
The new visibility setting.
joinPolicy
string
The (potentially adjusted) join policy that is now in effect.
StatusMeaning
400Invalid visibility value
401Missing or invalid session
403Caller is not the table host
404Table not found
When visibility is changed to "private", the joinPolicy is automatically set to "invite-only" if it was previously "open" or "friends-only". The adjusted joinPolicy is returned in the response body.

POST /api/tables/:tableId/add-bot

Host-only. Adds a bot player to an empty seat. The game starts automatically if all four seats are filled as a result. Bots use the ID bot:<seat> (e.g. bot:north). Bot behaviour: Bots bid the number of spades in their hand and play a random card from the set of legal plays. Bot turns are processed server-side immediately after each human action — clients do not need to poll separately. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the host) Request body
{ "seat": "north" }
Valid seat values: north, east, south, west. WebSocket events (when all 4 seats are filled)
  • GAME_STARTED — broadcast to the table room.
  • TURN_CHANGED — broadcast to the table room with { activeSeat, phase }.
Responses
200
object
Bot added.
tableId
string
UUID of the table.
seat
string
The seat the bot was placed in.
StatusMeaning
400Invalid seat value
401Missing or invalid session
403Caller is not the table host
404Table not found
409Game already in progress, or the requested seat is already taken
Bots are a development and testing convenience — they are intentionally minimal and not a production AI opponent. A table with all four bot seats will start immediately and play through to completion without human interaction.

POST /api/tables/:tableId/terminate

Ends the game and removes the table, regardless of the current phase (waiting or playing). All Redis keys associated with the table (game state, spectator links, invited-player list, lobby index entries) are deleted. All seated players’ presence is updated to online. Path parameter: tableId — UUID of the table. Required headers: x-session-id, x-player-id (must be the host) No request body required. Responses
StatusMeaning
200Game terminated. Body: { "message": "Game terminated." }
401Missing or invalid session
403Caller is not the table host
404Table not found
WebSocket event: A TABLE_REMOVED event is routed via the table’s current visibility channel (lobby broadcast for public tables, personal notification channels for friends-only tables) so that any lobby or friends-list UIs can remove the entry.

Build docs developers (and LLMs) love