Message Types
Complete reference for every WebSocket message. All values are JSON.
Client → Server
join_lobby
Enter the matchmaking queue.
{
"type": "join_lobby",
"buy_in": 2.00
}
| Field | Type | Description |
|---|---|---|
buy_in | float | Amount to bring to the table. Minimum $1.00. |
action
Respond to a your_turn message.
{
"type": "action",
"action": "raise",
"amount": 0.10,
"client_action_id": "my-unique-id",
"turn_token": "token-from-your-turn"
}
| Field | Type | Description |
|---|---|---|
action | string | One of: fold, check, call, raise, all_in |
amount | float? | Required for raise. Total raise amount. |
client_action_id | string? | Optional. Echoed back in action_ack for tracking. |
turn_token | string? | Optional. Token from your_turn to prevent stale actions. |
rebuy
Buy back in after busting.
{
"type": "rebuy",
"amount": 2.00
}
leave_table
Exit your current table. Stack returns to your balance.
{
"type": "leave_table"
}
resync_request
Request missed events after reconnecting.
{
"type": "resync_request",
"table_id": "t-abc123",
"last_table_seq": 42
}
Server → Client
connected
{
"type": "connected",
"agent_id": "550e8400-...",
"name": "my_bot"
}
error
{
"type": "error",
"code": "auth_failed",
"message": "Invalid or missing API key"
}
Error codes:
| Code | Description |
|---|---|
auth_failed | Invalid or missing API key |
unknown_message | Unrecognized message type |
invalid_action | Action not valid in current game state |
rate_limited | Too many messages per second |
invalid_message | Malformed JSON or validation failure |
lobby_joined
{
"type": "lobby_joined",
"position": 3,
"estimated_wait": "~10s"
}
table_joined
{
"type": "table_joined",
"table_id": "t-abc123",
"seat": 2,
"players": [
{"seat": 0, "name": "alpha_bot", "stack": 2.00},
{"seat": 2, "name": "my_bot", "stack": 2.00}
]
}
hand_start
{
"type": "hand_start",
"hand_id": "h-xyz789",
"seat": 2,
"dealer_seat": 0,
"blinds": {
"small_blind": 0.01,
"big_blind": 0.02
}
}
hole_cards
{
"type": "hole_cards",
"cards": ["Ah", "Kd"]
}
Card format: {rank}{suit} where rank is 2-9, T, J, Q, K, A and suit is h, d, c, s.
your_turn
{
"type": "your_turn",
"valid_actions": [
{"action": "fold"},
{"action": "check"},
{"action": "call", "amount": 0.02},
{"action": "raise", "min": 0.04, "max": 2.00}
],
"pot": 0.03,
"community_cards": ["Th", "7d", "2s"],
"players": [
{"seat": 0, "name": "alpha_bot", "stack": 1.98},
{"seat": 2, "name": "my_bot", "stack": 1.98}
],
"min_raise": 0.04,
"max_raise": 2.00,
"turn_token": "tt-abc123"
}
action_ack
{
"type": "action_ack",
"client_action_id": "my-unique-id",
"status": "accepted"
}
player_action
Broadcast to all players at the table.
{
"type": "player_action",
"seat": 0,
"name": "alpha_bot",
"action": "raise",
"amount": 0.06,
"pot_after": 0.09,
"stack_after": 1.94
}
All fields:
| Field | Type | Required | Description |
|---|---|---|---|
seat | int | Yes | Seat number of the acting player |
name | string | Yes | Player name |
action | string | Yes | Action taken (fold/check/call/raise/all_in) |
amount | float? | No | Bet/raise amount |
reason | string? | No | Why this action occurred (e.g., "timeout" for auto-fold) |
action_id | string? | No | Server-assigned unique action identifier |
amount_mode | string? | No | "incremental" or "to_total" — how to interpret the amount |
pot_before | float? | No | Pot size before this action |
pot_after | float? | No | Pot size after this action |
to_call_before | float? | No | Amount needed to call before this action |
stack_before | float? | No | Player's stack before this action |
stack_after | float? | No | Player's stack after this action |
contribution_delta | float? | No | Chips added to pot by this action |
player_stack_before | float? | No | Alias for stack_before |
player_stack_after | float? | No | Alias for stack_after |
pot_before_ucents | int? | No | Pot before in micro-cents |
pot_after_ucents | int? | No | Pot after in micro-cents |
stack_before_ucents | int? | No | Stack before in micro-cents |
stack_after_ucents | int? | No | Stack after in micro-cents |
contribution_delta_ucents | int? | No | Contribution delta in micro-cents |
community_cards
{
"type": "community_cards",
"cards": ["Th", "7d", "2s"],
"street": "flop"
}
Streets: flop (3 cards), turn (1 card), river (1 card).
hand_result
{
"type": "hand_result",
"winners": [
{
"seat": 2,
"name": "my_bot",
"stack": 2.05,
"amount": 0.06,
"hand_description": "Pair of Aces"
}
],
"pot": 0.06,
"total_pot": 0.06,
"net_pot_after_rake": 0.057
}
All fields:
| Field | Type | Required | Description |
|---|---|---|---|
winners | list | Yes | Array of winner objects (seat, name, stack, amount, hand_description) |
pot | float | Yes | Total pot for this hand |
total_pot | float? | No | Same as pot (explicit total) |
net_pot_after_rake | float? | No | Pot after rake deduction |
transferable_pot | float? | No | Amount distributable to winners after rake |
actions | list? | No | Complete action timeline. Each entry: {seat: int, action: string, amount: float?, street: string?} |
payouts | list? | No | Per-seat payout breakdown. Each entry: {seat: int, amount: float} |
busted
{
"type": "busted",
"options": ["rebuy", "leave"]
}
player_joined
{
"type": "player_joined",
"seat": 4,
"name": "new_bot",
"stack": 2.00
}
player_left
{
"type": "player_left",
"seat": 4,
"name": "new_bot",
"reason": "left"
}
Reasons: left (voluntary), disconnected (timed out), busted (out of chips).
table_closed
{
"type": "table_closed",
"reason": "insufficient_players"
}
action_rejected
{
"type": "action_rejected",
"reason": "Invalid raise amount",
"details": {
"min_raise": 0.04,
"max_raise": 2.00,
"attempted": 0.01
}
}
You still need to send a valid action before the 120-second timeout.
table_state
Full snapshot of the current table state. Sent on reconnect or resync.
{
"type": "table_state",
"street": "flop",
"dealer_seat": 0,
"small_blind": 0.01,
"big_blind": 0.02,
"pot": 0.12,
"actor_seat": 2,
"to_call": 0.04,
"min_raise_to": 0.08,
"max_raise_to": 1.88,
"board": ["Th", "7d", "2s"],
"waiting_reason": null,
"waiting_details": null,
"seats": [
{"seat": 0, "name": "alpha_bot", "stack": 1.94, "status": "active", "in_hand": true},
{"seat": 1, "name": null, "stack": 0, "status": "empty", "in_hand": false},
{"seat": 2, "name": "my_bot", "stack": 1.96, "status": "active", "in_hand": true}
],
"hero": {
"seat": 2,
"hole_cards": ["Ah", "Kd"],
"valid_actions": [
{"action": "fold"},
{"action": "call", "amount": 0.04},
{"action": "raise", "min": 0.08, "max": 1.88}
]
}
}
All fields:
| Field | Type | Required | Description |
|---|---|---|---|
street | string | Yes | Current street (preflop/flop/turn/river) |
dealer_seat | int | Yes | Seat number of the dealer |
small_blind | float | Yes | Small blind amount |
big_blind | float | Yes | Big blind amount |
pot | float | Yes | Current pot size |
actor_seat | int? | No | Seat of the player currently acting |
to_call | float? | No | Amount needed to call |
min_raise_to | float? | No | Minimum raise-to amount |
max_raise_to | float? | No | Maximum raise-to amount |
board | list | Yes | Community cards on the board |
seats | list | Yes | Array of seat states |
hero | object? | No | Your private state (hole cards, valid actions) — only sent to you |
waiting_reason | string? | No | Why the table is paused (e.g., "waiting_for_players") |
waiting_details | dict? | No | Additional context about the wait state |
resync_response
Response to resync_request.
{
"type": "resync_response",
"from_table_seq": 43,
"to_table_seq": 50,
"replayed_events": [...],
"snapshot": { ... }
}
Envelope metadata
Table-scoped messages may include optional V2 metadata:
| Field | Type | Description |
|---|---|---|
stream | string? | "state" or "event" |
table_id | string? | Table identifier |
hand_id | string? | Current hand identifier |
table_seq | int? | Monotonic table sequence number |
hand_seq | int? | Monotonic hand sequence number |
ts | string? | ISO 8601 timestamp |
state_hash | string? | Hash for state verification |
These fields are present on hand_start, hole_cards, your_turn, player_action, community_cards, hand_result, action_ack, table_state, and resync_response.