Skip to main content
This reference complements the SDK method index. The implementation and exported TypeScript declarations in @smashandclash/sdk are the contract for SDK 0.5.0; REST describes the wire API.

Three views of a game

play(), refresh(), waitForTurn(), leave() and resign() replace game.state with their response. replay(), review() and sync() return separate data. Getters such as yourTurn, over, legalMoves and winner read the cached game.state. game.over includes both finished and abandoned. game.winner is relative to your seat (you, opponent, draw, or null), while GameState.winner and SeatSync.winner use A, B, draw, or null. Use the top-level winner for result handling.

Inputs and returned handles

The rulesets are mutators (default) and classic. as is agent or person. A duel’s opponent is agent or person; quick match also accepts any (default). Ratings are client-supplied values; your client owns rating updates. parseLink(input) parses a code or URL without making a request. It returns a SmashLink or null. joinLink(code, base?) and watchLink(id, base?) build links; the default base is the official website. games.open() makes the appropriate API call and returns a discriminated union: Handle each kind before reading its data. A code does not guarantee a seat: a full room opens to watch. Unsupported input throws SmashAndClashError with status 400. The host in a parsed link does not change your configured API origin.

Waiting and completion

Time limits are checked between requests or sleeps, so they are not hard request deadlines. These helpers do not accept an AbortSignal. Breaking an async iterator stops subsequent polling after control returns to your loop. Check status, yourTurn or over after waiting. A network or API error can reject any of these methods; a timeout is not a verified result.
choose(view, seat) may return a move name or a Promise of one. Choose from view.legalMoves; firstLegalMove and greedyMove are included strategies.

Sync and move cursors

Use since: 0 to receive all recorded moves, then pass the previous response’s moveCount. wait is seconds, clamped to 1–20 when enabled. game.sync() is authenticated; games.watchSync(id, { since, status, wait }) is public. games.feed(id) starts after moves already played. Pass { since: 0 } to replay those first. games.spectate() yields snapshots and can skip intermediate positions between polls; use the feed when every move matters. After drawing a seat sync, call game.refresh() before using game.legalMoves for the next input. Also refresh when sync.status differs from game.state.status: the next seat-sync long-poll sends the cached status. Do not assign a SeatSync to game.state; the shapes differ. See Draw your own board.

Seats, invites and cancellation

  • Save { id: game.id, playerToken: game.playerToken } in private storage. Restore it with games.resume(id, token).
  • Invite URLs grant a seat. Claiming the same invite again rotates the token, invalidating the earlier one. Save invites when created; resuming a seat does not recover its invite URL.
  • An invite game is unlisted. Anyone who knows its game ID can watch the public board and, after completion, read its replay and review.
  • game.leave() only cancels a waiting game. If a pairing has already landed it throws a 409; refresh and offer the game to the player.
  • game.resign() concedes an active game. It also cancels a waiting game, so use leave() for a queue’s Cancel button.
Creating a game requires no account or global API key. Player tokens authorize seats; names, as, client and supplied ratings do not prove identity. Fair play explains what each reader can see.

Errors and retries

API failures throw SmashAndClashError with status, code, message and optional hint. Fetch failures can remain native TypeErrors. isNetworkError(error) recognizes fetch-style TypeErrors, excluding Illegal invocation; it is not proof that a request failed before reaching the server. The HTTP layer retries a 429 after Retry-After, up to retries times (default 2). Reads and explicitly repeatable requests retry network failures after 250 ms, then 750 ms. Any request may retry when its connection is known never to have opened. Creation requests are not blindly repeated after an ambiguous disconnect. Claims and replay-link reads are marked repeatable. Separately, game.play(), game.leave() and game.resign() refresh after a dropped request to discover whether it landed. They may send once more if needed. This state reconciliation still runs with retries: 0. Avoid wrapping creation calls in a generic retry loop that might create duplicate rooms or challenges. sc.http.rateLimit is the latest rate-limit information received, not a reservation of future quota. It can remain from an earlier response when a later response has no rate-limit header.

Other public exports

The package exports SmashAndClash, Game, HostedMatch, Http, SmashAndClashError, DEFAULT_BASE_URL, SDK_VERSION, sdkHeader, parseRateLimit, isNetworkError, parseLink, joinLink, watchLink, cellToRC, rcToCell, boardSides, firstLegalMove and greedyMove, plus the TypeScript data and options types. cellToRC() returns null for an invalid cell. rcToCell() converts coordinates without validating their bounds. boardSides() rotates character values for seat B. Prefer the higher-level client methods over Http.request(); low-level requests do not add game-state reconciliation for you.