Room API
WebSocket-based state synchronisation (v2 protocol). Rooms use in-memory state for fast reads/writes with periodic Durable Object Storage persistence for hibernation recovery. When all players disconnect, the room hibernates to zero cost and restores state on reconnect. Suitable for game lobbies, collaborative editors, and live dashboards.
Connection
wss://your-project.edgebase.dev/api/room?namespace=game&id=lobby-1
| Parameter | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Room namespace (e.g. game, chat) |
id | string | Yes | Room instance ID within the namespace |
Authentication
Send an auth message as the first WebSocket message:
{ "type": "auth", "token": "<accessToken>" }
On success:
{ "type": "auth_success", "userId": "user_123", "connectionId": "conn_abc" }
On re-authentication (token refresh during connection):
{ "type": "auth_refreshed", "userId": "user_123", "connectionId": "conn_abc" }
On failure:
{ "type": "error", "code": "AUTH_FAILED", "message": "Invalid or expired token" }
Authentication must be completed within 5 seconds (default) or the connection is closed with AUTH_TIMEOUT.
Client to Server Messages
auth
Authenticate the connection.
{ "type": "auth", "token": "<accessToken>" }
join
Join the room after authentication. Supports state recovery via last known versions.
{ "type": "join" }
With state recovery (reconnect/eviction recovery):
{
"type": "join",
"lastSharedState": { "score": 10 },
"lastSharedVersion": 5,
"lastPlayerState": { "inventory": [] },
"lastPlayerVersion": 3
}
| Field | Type | Required | Description |
|---|---|---|---|
lastSharedState | object | No | Last known shared state for recovery |
lastSharedVersion | number | No | Last known shared state version |
lastPlayerState | object | No | Last known player state for recovery |
lastPlayerVersion | number | No | Last known player state version |
send
Send an action to the server-side onAction handler. The server responds with action_result or action_error matched by requestId.
{
"type": "send",
"actionType": "MOVE",
"payload": { "x": 10, "y": 20 },
"requestId": "req-abc123"
}
| Field | Type | Required | Description |
|---|---|---|---|
actionType | string | Yes | The action type to execute |
payload | any | No | Action payload (defaults to {}) |
requestId | string | Yes | Unique ID for matching the response |
ping
Keep-alive heartbeat. Server responds with pong.
{ "type": "ping" }
Server to Client Messages
auth_success
First successful authentication.
{ "type": "auth_success", "userId": "user_123", "connectionId": "conn_abc" }
auth_refreshed
Successful re-authentication (token refresh).
{ "type": "auth_refreshed", "userId": "user_123", "connectionId": "conn_abc" }
sync
Full state snapshot. Sent on join or after hibernation recovery. Contains both shared and player state.
{
"type": "sync",
"sharedState": { "round": 1, "phase": "lobby" },
"sharedVersion": 1,
"playerState": { "inventory": ["sword"] },
"playerVersion": 1
}
| Field | Type | Description |
|---|---|---|
sharedState | object | Complete shared state visible to all players |
sharedVersion | number | Shared state version |
playerState | object | This player's private state |
playerVersion | number | Player state version |
shared_delta
Incremental shared state change using dot-path keys.
{
"type": "shared_delta",
"delta": { "player.position.x": 10 },
"version": 2
}
| Field | Type | Description |
|---|---|---|
delta | object | Changed dot-path keys and their new values (null = delete) |
version | number | New shared state version |
player_delta
Incremental player state change using dot-path keys.
{
"type": "player_delta",
"delta": { "inventory.0": "shield" },
"version": 2
}
| Field | Type | Description |
|---|---|---|
delta | object | Changed dot-path keys and their new values (null = delete) |
version | number | New player state version |
action_result
Successful action execution result. Sent only to the client that sent the action, matched by requestId.
{
"type": "action_result",
"requestId": "req-abc123",
"result": { "newPosition": { "x": 5, "y": 3 } }
}
action_error
Action execution failed. Sent only to the client that sent the action, matched by requestId.
{
"type": "action_error",
"requestId": "req-abc123",
"message": "Invalid move"
}
message
Server-sent message (sent by room.sendMessage() or room.sendMessageTo() in server-side code).
{
"type": "message",
"messageType": "game_over",
"data": { "winner": "user_1" }
}
kicked
This client was kicked from the room by server-side code.
{ "type": "kicked" }
error
Error notification.
{ "type": "error", "code": "RATE_LIMITED", "message": "Too many messages" }
pong
Response to ping.
Error Codes
| Code | Description |
|---|---|
AUTH_FAILED | Authentication failed or token invalid |
AUTH_REFRESH_FAILED | Re-authentication failed; existing auth preserved |
AUTH_TIMEOUT | Auth message not received within timeout |
INVALID_JSON | Message not valid JSON (closes connection with 4000) |
NOT_AUTHENTICATED | Operation requires auth (closes connection with 4000) |
RATE_LIMITED | Too many messages (default: 10 msg/s) |
INVALID_ACTION | send message missing actionType |
NO_HANDLER | No onAction handler registered for this action type |
UNAUTHENTICATED | Action requires authenticated userId |
JOIN_DENIED | Join rule evaluation failed |
ROOM_FULL | Room at maxPlayers capacity (HTTP 403) |
Limits
| Limit | Default |
|---|---|
| Max players per room | 100 |
| Max state size | 1 MB (configurable up to 10 MB) |
| Max message rate | 10 msg/s per connection |
| Max dot-path depth | 5 levels |
onAction timeout | 5 seconds |
| Auth timeout | 5 seconds |
| Send timeout (client) | 10 seconds |
| Pending connections per IP | 5 |
| Delta batch interval | 50ms |
| Hibernation idle timeout | 300 seconds |
| State save interval | 60 seconds |
| State TTL | 24 hours |
Metadata HTTP Endpoint
GET /api/room/metadata?namespace={ns}&id={roomId}
Retrieve room metadata without joining the room or establishing a WebSocket connection. Metadata is set server-side via room.setMetadata() and persisted to Durable Object storage.
| Query Parameter | Type | Required | Description |
|---|---|---|---|
namespace | string | Yes | Room namespace (e.g. game) |
id | string | Yes | Room instance ID within the namespace |
curl "https://your-project.edgebase.dev/api/room/metadata?namespace=game&id=lobby-1"
Response 200
{
"mode": "classic",
"playerCount": 5
}
Returns an empty object {} if no metadata has been set for the room.
The metadata endpoint does not require authentication. Developers control what data is exposed by choosing what to pass to room.setMetadata() in their server-side hooks. Only include publicly safe information — never store tokens, emails, or other sensitive data in metadata.
This endpoint is useful for lobby screens, matchmaking UIs, or any scenario where you need to query room information without opening a WebSocket connection.