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 state endpoint returns a player-specific view of the game. Cards belonging to other players’ hands are never included in any response — the server filters the full game state before sending it to each client. Spectators (observers) receive a public view with status: "spectating" that includes seats, scores, bids, and phase information, but never myHand or any other player’s hand data.
GET /api/tables/:tableId/state
Required headers: x-session-id, x-player-id
curl http://localhost:3000/api/tables/<tableId>/state \
-H 'x-session-id: <sessionId>' \
-H 'x-player-id: <playerId>'
Response codes
| Status | Meaning |
|---|
200 | Game state returned successfully. Body varies by phase — see Response variants below. |
401 | Missing or invalid session. |
403 | Player is not seated at or observing this table. |
404 | Table not found. |
Response variants
Waiting
Bidding
Playing
Spectator
Returned before the game starts (fewer than 4 seats filled).{
"status": "waiting",
"seats": {
"north": { "playerId": "uuid", "username": "alice", "isBot": false },
"east": null,
"south": { "playerId": "uuid", "username": "bob", "isBot": false },
"west": null
},
"observers": [],
"isHost": true,
"hostSeat": "north"
}
Always "waiting" when the game has not yet started.
Object keyed by seat name (north, east, south, west). Each value is either null (empty seat) or an object with playerId, username, and isBot.
List of spectators currently watching the table. Each entry has playerId and username.
Whether the requesting player is the table host.
The seat occupied by the host (e.g. "north"), or null if the host is not seated.
Returned during the bidding phase. Blind Nil eligible players who have not yet called reveal-hand will have myHand omitted and blindNilEligible: true.{
"phase": "bidding",
"handNumber": 1,
"dealerSeat": "north",
"myHand": [
{ "suit": "spades", "rank": "A" },
{ "suit": "spades", "rank": "K" },
{ "suit": "hearts", "rank": "9" },
{ "suit": "diamonds", "rank": "7" },
{ "suit": "clubs", "rank": "2" }
],
"bids": {
"north": null,
"east": 3,
"south": null,
"west": null
},
"currentBidderSeat": "south",
"blindNilEligible": false,
"scores": { "ns": 0, "ew": 0 },
"bags": { "ns": 0, "ew": 0 },
"isHost": false,
"hostSeat": "north",
"observers": []
}
The current hand number, starting at 1.
Seat of the current dealer. Bidding begins with the player to the dealer’s left.
The requesting player’s cards as an array of card objects ({ suit, rank }). Omitted when the player is eligible for Blind Nil and has not yet called reveal-hand.
Object keyed by seat. null means the player has not yet bid. "nil" = Nil bid, "blind_nil" = Blind Nil bid, integers 0–13 = regular bid for that number of tricks.
The seat whose turn it is to bid.
true when the requesting player’s team is at least 100 points behind and the player has not yet bid. When true, myHand is withheld until the player calls reveal-hand or bids Blind Nil.
Cumulative scores entering this hand: { ns: number, ew: number }.
Accumulated bags entering this hand: { ns: number, ew: number }.
Whether the requesting player is the table host.
The seat occupied by the table host, or null if unavailable.
Enriched seat map with player info for all four seats: same shape as seats in the waiting/spectator views ({ playerId, username, isBot } or null per seat).
List of spectators currently watching the table.
Returned during the playing phase. Includes the current trick in progress, completed trick counts, and — when it is the requesting player’s turn — the validCards array.{
"phase": "playing",
"handNumber": 1,
"dealerSeat": "north",
"myHand": [
{ "suit": "spades", "rank": "A" },
{ "suit": "hearts", "rank": "Q" },
{ "suit": "diamonds", "rank": "10" },
{ "suit": "clubs", "rank": "8" }
],
"bids": {
"north": 3,
"east": "nil",
"south": 4,
"west": 3
},
"teamBids": { "ns": 4, "ew": 3 },
"tricksWon": { "north": 1, "east": 0, "south": 2, "west": 1 },
"currentTrick": [
{ "seat": "east", "card": { "suit": "hearts", "rank": "7" } },
{ "seat": "south", "card": { "suit": "hearts", "rank": "J" } }
],
"currentPlayerSeat": "west",
"leadSeat": "east",
"spadesbroken": false,
"isFirstTrick": false,
"scores": { "ns": 0, "ew": 0 },
"bags": { "ns": 0, "ew": 0 },
"isHost": false,
"hostSeat": "north",
"observers": [],
"validCards": [
{ "suit": "hearts", "rank": "Q" }
]
}
The requesting player’s remaining cards as an array of card objects ({ suit, rank }).
Final bids for all four seats. Values are integers 0–13, "nil", or "blind_nil".
Effective team bid for each partnership: { ns: number | null, ew: number | null }. null when both partners bid Nil (double Nil — no team target).
Number of tricks taken so far this hand, keyed by seat.
Cards played in the current (incomplete) trick, in play order. Each element is { seat: string, card: { suit: string, rank: string } }.
The seat whose turn it is to play.
Whether Spades have been broken this hand (i.e. a Spade has been played on any non-first trick). A Spade played on the first trick does not break Spades.
Cumulative scores entering this hand: { ns: number, ew: number }.
Accumulated bags entering this hand: { ns: number, ew: number }.
Whether the requesting player is the table host.
The seat occupied by the table host, or null if unavailable.
Enriched seat map with player info for all four seats: same shape as seats in the waiting/spectator views ({ playerId, username, isBot } or null per seat).
List of spectators currently watching the table.
Array of legal card objects ({ suit, rank }) the requesting player may play. Only present when currentPlayerSeat matches the requesting player’s seat. See the validCards field section below. Returned for observers. Never includes myHand or any player’s hand. The status field is set to "spectating" and isHost is always false for spectators.{
"status": "spectating",
"phase": "playing",
"seats": {
"north": { "playerId": "uuid", "username": "alice", "isBot": false },
"east": { "playerId": "uuid", "username": "bob", "isBot": false },
"south": { "playerId": "uuid", "username": "carol", "isBot": false },
"west": { "playerId": "uuid", "username": "dave", "isBot": false }
},
"observers": [],
"isHost": false,
"hostSeat": "north",
"scores": { "ns": 120, "ew": 90 },
"bags": { "ns": 2, "ew": 1 },
"bids": { "north": 3, "east": "nil", "south": 4, "west": 3 },
"tricksWon": { "north": 2, "east": 0, "south": 3, "west": 2 },
"currentTrick": [],
"currentPlayerSeat": "north",
"spadesbroken": true,
"handNumber": 2
}
Always "spectating" for observer connections.
Current game phase: "bidding", "playing", "blind_nil_exchange", or "game_over".
All four seats with player info. Values are null (empty) or { playerId, username, isBot }.
List of spectators currently watching the table.
Always false for spectators.
The seat occupied by the table host, or null if unavailable.
Cumulative scores: { ns: number, ew: number }.
Accumulated bags: { ns: number, ew: number }.
validCards field
validCards is present in the response only when it is the requesting player’s turn to play during the playing phase. The server computes the full set of legal plays via getLegalPlays and includes them as an array of card objects (e.g. [{ "suit": "hearts", "rank": "Q" }]). Clients may use this array to highlight or restrict UI affordances — for example, greying out cards that are not in the list. However, the server re-validates every card play regardless; passing a card not in validCards to POST /api/tables/:tableId/play returns 400.
Card representation
Cards are represented as JSON objects with two fields:
| Field | Type | Values |
|---|
suit | string | "spades", "hearts", "clubs", "diamonds" |
rank | string | "2"–"9", "10", "J", "Q", "K", "A" |
Examples:
{ "suit": "spades", "rank": "A" }
{ "suit": "hearts", "rank": "K" }
{ "suit": "diamonds", "rank": "10" }
{ "suit": "clubs", "rank": "2" }
Note that 10 is represented as the string "10" (two characters), not as a single-character code.