Reverse-engineered documentation of the Jackbox Games ecast platform API. This covers the REST API at `ecast.jackboxgames.com` and the WebSocket protocol used by `jackbox.tv` clients to participate in game rooms.
7. [Appendix: Raw Captures](#appendix-raw-captures)
---
## Overview
Jackbox Games uses a platform called **ecast** to manage game rooms and real-time communication between the game host (running on a console/PC) and players (connecting via `jackbox.tv` in a browser).
- **Load balancer** (`ecast.jackboxgames.com`): Routes REST requests and resolves room codes to specific game servers.
- **Game server** (e.g., `ecast-prod-use2.jackboxgames.com`): Hosts WebSocket connections and manages room state. Returned in the `host` / `audienceHost` fields of room info.
- **Room code**: 4-letter alphanumeric code (e.g., `LSBN`) that identifies an active game session.
### Base URLs
| Purpose | URL |
|---------|-----|
| REST API (rooms) | `https://ecast.jackboxgames.com/api/v2/` |
Room information with audience data and join role. Uses a different response format (no `ok`/`body` wrapper).
**Response:**
```json
{
"roomid": "LSBN",
"server": "ecast-prod-use2.jackboxgames.com",
"apptag": "drawful2international",
"appid": "fdac94cc-cade-41ff-b4aa-e52e29418e8a",
"numAudience": 0,
"audienceEnabled": true,
"joinAs": "player",
"requiresPassword": false
}
```
| Field | Type | Description |
|-------|------|-------------|
| `numAudience` | number | Current audience member count |
| `joinAs` | string | Default role for new connections (`"player"`) |
| `requiresPassword` | boolean | Whether password is needed |
> **Note:** `numAudience` may not update in real-time for WebSocket-connected audience members. Consider using the WebSocket `room/get-audience` opcode for live counts.
> **Important:** This counts allocated **slots**, not currently-active WebSocket connections. Jackbox holds player slots open indefinitely for reconnection — there is no concept of "leaving" a game. A player who closes their browser tab still occupies a slot. The count only decreases if the room itself is destroyed. Therefore, `connections - 1` gives the number of players who have **ever joined** the room, not the number currently online.
| `secret` | Yes | Session secret from original `client/welcome` response |
| `id` | Yes | Session ID from original `client/welcome` response |
| `name` | Yes | Display name (can differ from original) |
| `format` | Yes | `json` |
On reconnection, the server returns `client/welcome` with `reconnect: true` and the same `id` and `secret`. The player resumes their existing slot rather than consuming a new one.
> **Important: `client/connected` and `client/disconnected` are NOT delivered to player connections.** Extensive testing across multiple rooms confirmed that these opcodes never fire for players joining, leaving, or reconnecting — even with graceful WebSocket close. They exist in the client JavaScript and may be delivered to the **host** connection only, but this could not be verified. Players learn about joins exclusively through `textDescriptions` entity updates ("X joined."). There is **no notification mechanism for player disconnection** — Jackbox's design holds player slots open for reconnection indefinitely, and the concept of "leaving" does not exist in the UI or protocol.
Players can reconnect to their existing session using the `secret` and `id` from their original `client/welcome` message. There are two reconnection methods:
**Method 1 — Via `secret` and `id` query params (programmatic):**
If the browser retains the same `deviceId` (stored in cookies/localStorage), the server automatically matches the reconnecting client to their previous session. The jackbox.tv UI shows a "RECONNECT" button instead of "PLAY" when it detects an existing session.
In both cases, the server responds with `client/welcome` containing:
**Jackbox has no concept of "leaving" or "disconnecting."** When a player's WebSocket closes (gracefully or otherwise), their player slot is held open indefinitely for reconnection. From the game's perspective, the player is still "in the game" — just temporarily unreachable.
- The `connections` REST endpoint count does **not** decrease when a player's WebSocket closes.
- The `here` field in `client/welcome` continues to list disconnected players.
- No `client/disconnected` event is sent to other players.
- No `textDescriptions` update is generated for disconnections.
- The player's `player:{id}` entity remains intact with its full state.
The only way a player slot is freed is if the room itself is destroyed (host closes the game) or the room times out.
> **REST/WebSocket state divergence:** A room can be reported as existing by the REST API (`GET /rooms/{code}` returns 200) while the WebSocket layer considers it ended (error code 2027). Always verify with a WebSocket connection attempt if the REST response seems stale.
Answers to common questions about managing players and rooms.
### How to detect when players join or leave
**Join detection:**
- **WebSocket (best):** Watch for `textDescriptions` entity updates with `category: "TEXT_DESCRIPTION_PLAYER_JOINED"` or `"TEXT_DESCRIPTION_PLAYER_JOINED_VIP"`.
Jackbox does not have a concept of "leaving." Player slots are held open indefinitely for reconnection. There is:
- **No** `client/disconnected` event sent to players.
- **No** change in the `connections` REST count when a player's WebSocket closes.
- **No** `textDescriptions` update for disconnections.
- **No** change in the `here` field — disconnected players remain listed.
In other words, **the ecast API provides no mechanism to detect that a player has disconnected.** The game treats all players as permanently in the room once joined. This is by design — the Jackbox UI has no "leave" button; a player who closes their browser can always reconnect.
**Possible workarounds:**
- Monitor game-specific player entity state changes (e.g., a drawing game might detect timeouts for players who don't submit).
- The game host (console) may have internal logic to handle absent players, but this is not exposed through the ecast API.
`N` = host(1) + all player slots ever allocated + audience + shards. This count **does not decrease** when players disconnect. `N - 1` gives the approximate number of player slots allocated.
Count entries where `roles` contains `player`. This includes **both connected and disconnected** players — `here` reflects all allocated slots, not just active WebSocket connections. The `here` field excludes the connecting client itself (you don't see yourself).
**Counting currently-connected players:**
There is no reliable API method to distinguish between connected and disconnected players. The `connections` count and `here` field both include disconnected players whose slots are held for reconnection.
**WebSocket:** Watch `room` entity for `gameFinished: true`
**REST:** The room will either remain (if "same players" is chosen) or disappear (404 on room endpoint) if the host closes it.
### Game stats and player stats
**During gameplay:** Player entities (`player:{id}`) contain game-specific state including `history`, `playerInfo`, and state-specific data.
**After game completion:** The `room` entity may contain result data. Player entities may contain `history` arrays with per-round results. The `analytics` array in the room entity tracks game screens/phases.
Specific stat fields are game-dependent (Drawful 2, Quiplash, etc. have different schemas).
-`shard` — Internal connection (appears when audience is connected, possibly audience aggregator)
> **`here` includes disconnected players.** Tested by connecting 3 players, closing all their WebSockets, then having a new player connect. The new player's `here` field listed all 3 previous players despite none of them having active WebSocket connections. This is consistent with Jackbox's slot-reservation model where player slots persist for reconnection.