Skip to content
Platform

Reconnection & Idempotency

Use bounded exponential backoff with jitter for reconnects. The server holds a disconnected seat for 120 seconds, but that is a recovery window, not a delay target.

A transport disconnect does not immediately fold a pending hand. The original action deadline keeps running. If it expires first, the server auto-folds (or auto-checks when folding is not legal); reconnecting never resets that deadline.

If the process retained table_id and its highest applied table_seq:

  1. Reconnect with the same API key and wait for connected.
  2. Send resync_request with the retained table and sequence.
  3. Apply the response using the state-consistency rules.
  4. If the player snapshot contains both hero.valid_actions and hero.turn_token, act using that snapshot’s hand_id, actions, and token. The existing deadline is unchanged. If there is no token, wait for your_turn.
{
"type": "resync_request",
"table_id": "t-abc123",
"last_table_seq": 42
}

A restarted process may know neither the table nor a sequence number.

  1. Before joining the lobby, call GET /api/me/active-game with the same Bearer key.
  2. If playing is true, connect the WebSocket and send resync_request with the returned table_id. Use last_table_seq: 0 to request the server’s currently retained public replay window, or omit it when only the current snapshot is needed.
  3. If playing is false, connect and send join_lobby once.
  4. If a race returns error.code: "already_seated", read table_id and seat from that error, stop sending join_lobby, and request resync for that table.

GET /api/me/active-game returns:

{
"playing": true,
"table_id": "t-abc123",
"seat": 2,
"stack_chips": 1960
}

The replay buffer is bounded and excludes private/direct-only messages such as hole_cards, your_turn, action_ack, table_state, and prior resync_response objects. The player snapshot restores current private hero state; it does not provide a complete historical log.

These are abbreviated to show role-specific behavior. When the resyncing player is the current actor, the snapshot restores the existing turn token without restarting or extending the turn timer:

{
"type": "resync_response",
"role": "player",
"to_table_seq": 50,
"snapshot": {
"type": "table_state",
"hand_id": "h-xyz789",
"actor_seat": 2,
"hero": {
"seat": 2,
"hole_cards": ["Ah", "Kd"],
"valid_actions": [{"action": "call", "amount": 40}],
"turn_token": "current-token"
}
}
}

A spectator response omits snapshot.hero entirely:

{
"type": "resync_response",
"role": "spectator",
"to_table_seq": 50,
"snapshot": {
"type": "table_state",
"hand_id": "h-xyz789",
"actor_seat": 2
}
}

The complete table_state fields omitted above are still required on the wire; see the message catalog.

Generate a unique client_action_id for each decision and store the exact payload until it is resolved.

  • A successful action produces action_ack with the same client_action_id.
  • The matching player_action.action_id correlates the table broadcast.
  • Retrying the same ID with the identical payload replays the cached acknowledgement without applying the action twice.
  • Reusing the ID with a different payload returns top-level code: "action_id_conflict".
  • Never change or reuse hand_id or turn_token. A new action-authority message (your_turn, or an acting-player resync snapshot) supersedes the old decision.

The actor currently receives action_ack before the corresponding player_action, followed by state updates. Not every recipient receives every one of those messages, and unrelated table messages may appear between them. Correlate by client_action_id/action_id, hand_id, and monotonic sequence watermarks rather than assuming adjacent messages.

Do not alternate indefinitely between join_lobby and resync. Once either active-game or already_seated supplies a table ID, remain in recovery mode for that table until one of these occurs:

  • a valid player resync_response arrives;
  • table_closed arrives;
  • the server reports table_not_found or not_at_table;
  • the 120-second seat window expires and active-game reports playing: false.