During the playing phase, each player takes turns playing one card per trick in clockwise seat order. The server validates every card play server-side via the anti-cheat validator inDocumentation 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.
server/anticheat/validate.js before mutating any game state — there is no way for a client to submit an illegal move and have it accepted. After each human card play, the server automatically resolves all consecutive bot turns, so the response you receive already reflects the state after bots have acted.
Card format
Cards are represented as JSON objects with
suit and rank fields. Suits are full lowercase strings: "spades", "hearts", "diamonds", "clubs". Ranks are "2"–"9", "10", "J", "Q", "K", "A". Note that 10 is the two-character string "10", not a single-character code.Examples:POST /api/tables/:tableId/play
Submit a card play for the requesting player’s turn. Required headers:x-session-id, x-player-id
Body
The card to play as a
{ suit, rank } object. The card must be in the player’s current hand and must be a legal play under the current game rules (see Legal play rules below).Server-side validation
Before any state mutation the server checks (in order):- The game must be in the
playingphase. - It must be the requesting player’s turn (
currentPlayerSeat). - The card must exist in the player’s hand.
- The card must be a legal play under current game rules (follow-suit obligation, Spades-breaking rules, and first-trick restrictions are all enforced).
409 is returned if checks 1 or 2 fail; a 400 is returned if checks 3 or 4 fail.
Bot turns
After the player’s card is accepted, the server resolves all consecutive bot turns before returning the response. The response body therefore reflects the game state after all bot plays have been applied — you do not need to poll while bots act.Example
Response codes
| Status | Meaning |
|---|---|
200 | Card accepted. Body: updated player view (see Game State). All bot turns auto-advanced before response. |
400 | Card is not in the player’s hand, or the play is illegal (must follow suit, Spades not yet broken, first-trick Spade restriction). |
401 | Missing or invalid session. |
403 | Player is not seated at this table. |
404 | Table not found. |
409 | It is not this player’s turn to play, or the game is not in the playing phase. |
Legal play rules
Play legality rules
Play legality rules
The following rules govern which cards may be played on any given turn. These are enforced identically by the
getLegalPlays function (used to compute validCards in the state response) and by isCardLegal (called during validateCardPlay before every state mutation).Follow-suit obligation
If the player’s hand contains any cards matching the suit led in the current trick, the player must play one of those cards. They may not play a card of a different suit when they can follow suit.Spades cannot be led until broken
A player may not lead a Spade to open a trick until Spades have been broken (i.e. a Spade has been played while unable to follow suit on a previous, non-first trick). Exception: if the player’s entire hand consists only of Spades, they may lead a Spade regardless of whether Spades are broken.First-trick Spade restriction
On the very first trick of a hand, Spades cannot be led. Additionally, if a player cannot follow the led suit on the first trick, they may not play a Spade as long as they hold at least one non-Spade card. If their entire hand is Spades, they may play a Spade. Playing a Spade on the first trick (only possible when a player holds only Spades) does not break Spades.validCards shows all legal options
The validCards array in the Game State response lists every card the current player is permitted to play. Clients may use this to highlight legal cards in the UI. The server re-validates independently — submitting a card not in validCards returns 400.Anti-cheat
server/anticheat/validate.js exposes two validators called before any state mutation: validateBidTurn (for bids) and validateCardPlay (for card plays).validateCardPlay performs four checks in order:- Game phase must be
"playing". - The requesting seat must match
currentPlayerSeat(turn order enforcement). - The submitted card must exist in the player’s hand (
CARD_NOT_IN_HAND). - The card must be legal under
isCardLegal— which applies follow-suit, Spades-breaking, and first-trick restrictions (ILLEGAL_PLAY).
validateBidTurn checks that the game is in "bidding" phase and that the requesting seat is currentBidderSeat.Any violation throws a structured error with a machine-readable code (INVALID_ACTION, NOT_YOUR_TURN, CARD_NOT_IN_HAND, or ILLEGAL_PLAY) which the route handler maps to a 409 or 400 response. State is never mutated on a failed validation.Trick resolution
After the 4th card is played to a trick the server resolves the winner immediately:- Highest Spade wins if any Spades were played in the trick.
- Highest card of the led suit wins if no Spades were played.
- Cards of other non-led suits never win regardless of rank.
tricksWon count is incremented, the trick is appended to completedTricks, and the winner becomes currentPlayerSeat (they lead the next trick). If a Spade was played on any non-first trick and Spades were not yet broken, spadesbroken is set to true. A Spade played on the first trick (only possible when the player holds nothing but Spades) does not break Spades.
End of hand
After all 13 tricks have been played the server automatically runs end-of-hand scoring with no additional client action required:scoreHand— computesscoreDeltaandnewBagsfor each team based on bids, actual tricks won, and Nil/Blind Nil outcomes.applyBagPenalties— accumulates bags; every 10 accumulated bags deducts 100 points from that team’s score and resets the bag count for that group of 10.checkWinLoss— evaluates win and loss conditions:- A team reaching 250 or more points wins; if both teams cross 250 in the same hand the higher score wins (exact tie plays another hand).
- A team falling to −250 or below loses immediately by the same tie-break rules.
phase: "game_over" with gameOver: true and winner set to the winning team ("ns" or "ew"). Otherwise the dealer rotates clockwise, a new hand is dealt, and the game returns to phase: "bidding" automatically — no client action is needed to start the next hand.