Khelau Call Break game socket AsyncAPI 3.0.0 · protocol v1.0.0
Servers: wss://khelau-api.vercel.app/api/ws (production) · ws://localhost:8787/api/ws (local) · Machine-readable: /v1/asyncapi.json
Real-time play for Call Break on Khelau. One WebSocket carries the whole session: identify, join or create a room, then bid and play. The server is authoritative: it validates every move, keeps hidden cards server-side and sends each player only their own view.
Connection
- URL:
wss://khelau-api.vercel.app/api/ws (production), ws://localhost:8787/api/ws (local). Also served at /ws and /v1/callbreak/ws. - Origin: browsers must connect from an origin listed in the API's
ALLOWED_ORIGINS; others are refused with HTTP 403. - Frames: UTF-8 JSON objects with a
t (type) field, at most 4 KB each. Malformed or unknown messages get an error. - Limits: about 20 messages per second per socket (bursts of 40); connections and room creation are rate-limited per IP.
Session lifecycle
- Connect and send
hello (with the stored token, if any). Receive welcome. quick, create or join a room. Receive room updates.- In a game, receive
state after every change and send bid/play on your turn, echoing seq.
Reconnecting
Connections can drop at any time; Vercel also closes each socket after 5 minutes. Reconnect with backoff and send hello with the same token: you get your seat, hand and timers back. If the server that hosted the game went away, the game is restored from the database.
Messages
Client → Server
Server → Client
Client → Server
hello Hello Client → Server
Identify the player. Must be the first message on every connection.
Send the stored session token to return to your seat after a refresh, a dropped connection or a server restart; send null the first time. The server answers with welcome, then room (and state when at a table).
| Field | Type | Description |
|---|
t | "hello" | |
v | 1 | Protocol version. |
token | string | null | Session token from a previous welcome, or null.max length 128 |
name | string | Display name; trimmed and cut to 16 characters. |
avatar | integer | Avatar number. Photos never leave the device. min 0, max 18 |
clientId optional | string | Anonymous per-browser id, used for statistics only. pattern ^[A-Za-z0-9_-]{8,40}$ |
Example
{
"t": "hello",
"v": 1,
"token": null,
"name": "Asha",
"avatar": 10,
"clientId": "k3v9x0q2m1"
}
quick Find a game Client → Server
Join the public lobby with the most players waiting, or open a new one.
| Field | Type | Description |
|---|
t | "quick" | |
Example
{
"t": "quick"
}
create Create private room Client → Server
Open a private room; its code is in the next room message.
| Field | Type | Description |
|---|
t | "create" | |
Example
{
"t": "create"
}
join Join room Client → Server
Join a room by code.
| Field | Type | Description |
|---|
t | "join" | |
code | string | The 5-character room code (case-insensitive). max length 16 |
Example
{
"t": "join",
"code": "KTM42"
}
founders Join Founders lobby Client → Server
Join the invite-only Founders lobby.
The password is checked only on the server. 5 wrong tries per 10 minutes.
| Field | Type | Description |
|---|
t | "founders" | |
name | string | max length 64 |
password | string | max length 256 |
Example
{
"t": "founders",
"name": "Sabin",
"password": "********"
}
sitOut Sit out Client → Server
Founders lobby: move from a seat to the bench (before a game).
| Field | Type | Description |
|---|
t | "sitOut" | |
Example
{
"t": "sitOut"
}
sitIn Take a seat Client → Server
Founders lobby: move from the bench to a free seat.
| Field | Type | Description |
|---|
t | "sitIn" | |
Example
{
"t": "sitIn"
}
start Start Client → Server
Start now; bots fill empty seats. Private rooms: host only.
| Field | Type | Description |
|---|
t | "start" | |
Example
{
"t": "start"
}
bid Bid Client → Server
Bid for the round when it is your turn.
| Field | Type | Description |
|---|
t | "bid" | |
bid | integer | min 1, max 13 |
seq optional | integer | The seq of the latest state this client saw. A move made against an older state is ignored and the current state is sent again. Optional for older clients.min 0 |
Example
{
"t": "bid",
"bid": 3,
"seq": 12
}
play Play card Client → Server
Play a card from your hand when it is your turn. It must be one of state.view.legal.
| Field | Type | Description |
|---|
t | "play" | |
card | Card | |
seq optional | integer | The seq of the latest state this client saw. A move made against an older state is ignored and the current state is sent again. Optional for older clients.min 0 |
Example
{
"t": "play",
"card": {
"suit": "S",
"rank": 14
},
"seq": 13
}
ready Next round Client → Server
Ready for the next round (between rounds).
| Field | Type | Description |
|---|
t | "ready" | |
Example
{
"t": "ready"
}
again Play again Client → Server
Start a new game in the same room after game over (host only).
| Field | Type | Description |
|---|
t | "again" | |
Example
{
"t": "again"
}
leave Leave Client → Server
Leave the room. Mid-game, a bot takes the seat and it is held for you for 2 minutes (see rejoin).
| Field | Type | Description |
|---|
t | "leave" | |
Example
{
"t": "leave"
}
rejoin Rejoin Client → Server
Take back the seat you left, while the rejoin offer is open.
| Field | Type | Description |
|---|
t | "rejoin" | |
Example
{
"t": "rejoin"
}
ping Ping Client → Server
Keep-alive; answered with pong.
| Field | Type | Description |
|---|
t | "ping" | |
Example
{
"t": "ping"
}
Server → Client
welcome Welcome Server → Client
Reply to hello. Store token (e.g. in localStorage) and send it in every later hello.
| Field | Type | Description |
|---|
t | "welcome" | |
token | string | Session token: it identifies your seat. Treat it as a secret. |
playerId | string | |
Example
{
"t": "welcome",
"token": "q1w2e3r4t5y6u7i8o9p0as",
"playerId": "5f2a9c0d1e3b"
}
room Room Server → Client
The room you are in: seats, host, status and countdowns. null after leaving.
| Field | Type | Description |
|---|
t | "room" | |
room | RoomInfo | null | |
Example
{
"t": "room",
"room": {
"code": "KTM42",
"isPublic": false,
"status": "lobby",
"hostSeat": 0,
"you": 0,
"seats": [
{
"name": "Asha",
"avatar": 10,
"kind": "human",
"connected": true,
"botControlled": false
},
null,
null,
null
],
"autoStartMs": null,
"founders": false,
"bench": null
}
}
state Game state Server → Client
Your view of the game. It contains only your own hand; other players' cards are never sent.
| Field | Type | Description |
|---|
t | "state" | |
view | PlayerView | |
turnMsLeft | integer | null | Milliseconds left for the player whose turn it is. |
ready | array of boolean | Between rounds: which seats pressed Next round. |
nextRoundMs | integer | null | Milliseconds until the next round starts automatically. |
seq optional | integer | Action counter; echo it in bid and play.min 0 |
Example
{
"t": "state",
"view": {
"seat": 0,
"round": 1,
"phase": "bidding",
"turn": 0,
"hand": [
{
"suit": "S",
"rank": 14
}
],
"bids": [
null,
3,
null,
null
],
"totals": [
0,
0,
0,
0
],
"legal": []
},
"turnMsLeft": 29000,
"ready": [
false,
false,
false,
false
],
"nextRoundMs": null,
"seq": 12
}
rejoin Rejoin offer Server → Client
After leaving a game in progress: your seat is held for offer.ms more milliseconds. null: nothing to rejoin.
| Field | Type | Description |
|---|
t | "rejoin" | |
offer | object | null | |
Example
{
"t": "rejoin",
"offer": {
"code": "KTM42",
"ms": 118000
}
}
watch Watch Server → Client
Founders lobby: public progress for the founder sitting out (bids, tricks, totals; never cards).
| Field | Type | Description |
|---|
t | "watch" | |
watch | object | null | |
Example
{
"t": "watch",
"watch": null
}
error Error Server → Client
A request was refused (e.g. room full, not your turn, illegal card). Human-readable.
| Field | Type | Description |
|---|
t | "error" | |
message | string | |
Example
{
"t": "error",
"message": "That room is full or already playing"
}
pong Pong Server → Client
Reply to ping, with the number of players online on this server instance.
| Field | Type | Description |
|---|
t | "pong" | |
online | integer | min 0 |
Example
{
"t": "pong",
"online": 3
}
Types
Card
| Field | Type | Description |
|---|
suit | "S" | "H" | "C" | "D" | Spades (always trump), Hearts, Clubs, Diamonds. |
rank | integer | 11 = J, 12 = Q, 13 = K, 14 = A. min 2, max 14 |
Seat
| Field | Type | Description |
|---|
name | string | |
avatar | integer | |
kind | "human" | "bot" | |
connected | boolean | |
botControlled | boolean | A human seat played by a bot for now (disconnected or left). |
left optional | boolean | |
awayMs optional | integer | null | Milliseconds left before a held seat is given up. |
RoomInfo
| Field | Type | Description |
|---|
code | string | |
isPublic | boolean | |
status | "lobby" | "playing" | "finished" | |
hostSeat | integer | min 0, max 3 |
you | integer | Your seat (4 = the Founders bench). min 0, max 4 |
seats | array of Seat | |
autoStartMs | integer | null | Public lobbies: milliseconds until bots fill the seats and the game starts. |
founders | boolean | |
bench | Seat | |
PlayerView
Everything one seat may know. Other hands appear only as counts.
| Field | Type | Description |
|---|
seat | integer | min 0, max 3 |
round | integer | min 1 |
dealer optional | integer | |
phase | "bidding" | "playing" | "trickEnd" | "roundEnd" | "gameOver" | |
hand | array of Card | Your cards only. |
handCounts optional | array of integer | Cards left per seat. |
bids | array of integer | null | |
won optional | array of integer | Tricks won this round, per seat. |
turn | integer | |
trick optional | array | Cards on the table: {seat, card}. |
totals | array of integer | Scores in tenths (41 = +4.1). |
legal | array of Card | Cards you may play now (empty unless it is your turn to play). |