# Replay Poker (casino.org) — traffic interpretation reference

This document specifies how to read network traffic for **Replay Poker** on
`https://www.casino.org/replaypoker/...` and related API hosts, so that a hand
or session can be reconstructed **from packets alone** (REST responses +
WebSocket frames). It does not assume any particular capture tool.

**TL;DR**

- **HTTP**: game and account context under `https://www.casino.org/replaypoker/api/...`.
- **WebSocket**: `wss://apio.replaypoker.com/apio/websocket?...` carries live
  table state as **Phoenix Channels** messages: a JSON array
  `[joinRef, ref, topic, event, payload]`.
- **Authoritative play** advances through `output` frames whose `payload.updates[]`
  entries describe discrete steps (`action` field). Client `input` frames carry
  the local player’s committed actions.

---

## 1. Traffic families and lifecycle

### 1.1 Two transports

| Transport | Typical host | Role |
| --- | --- | --- |
| HTTPS JSON | `www.casino.org` | JWT, profiles, settings, buy-in limits, missions, etc. |
| WSS (Phoenix) | `apio.replaypoker.com` | Real-time table: `state`, streamed `output` deltas, client `input`, room `broadcast` |

The WebSocket URL includes a `token` query parameter; that token is obtained
from the HTTP API (see §3.1). Without a valid token the socket will not join a
table topic.

### 1.2 Chronological roles (reconstruction)

1. **Bootstrap (HTTP)** — identity, table id in the page URL, wallet/sit
   requirements, user settings.
2. **Socket open** — client connects to `apio.replaypoker.com`.
3. **`state` (server → client)** — full snapshot after joining channel topic
   `table:<tableId>` (see §4.1).
4. **`output` (server → client)** — ordered list of `updates[]` since previous
   frame; this is the main event log (§4.2).
5. **`input` (client → server)** — actions initiated by the client that owns
   the capture (§4.3).
6. **`broadcast` (server → client)** — room-level signals (e.g. tournament)
   (§4.4).
7. **Resume** — client may send an `updates` event with `{ handId, sequence }`
   after reconnect (§4.5); payload carries **no new facts** about the hand,
   only sync pointers.

### 1.3 Timestamps

| Source | Field / meaning | Unit |
| --- | --- | --- |
| HTTP | `Date` response header, or client wall clock at capture | Usually HTTP-date or ms epoch in logs |
| Phoenix `tick` updates | `timeoutAt` | Epoch **milliseconds** (turn timer deadline) |
| Generic capture logs | Per-row `received` / `endTs` / `ts` | Tool-defined; often **ms epoch** |

When correlating HTTP and WS, prefer **monotonic capture order** inside one
session if absolute clocks disagree.

---

## 2. HTTP API (interpretation)

Paths below are relative to `https://www.casino.org` unless noted.

### 2.1 Methods, status, bodies

- **Methods**: `GET`, `POST`, and occasionally others for niche routes.
- **Success**: status `2xx`; errors `4xx`/`5xx` with JSON or plain text bodies
  depending on route.
- **Content-Type**: request/response bodies observed as `application/json` for
  game APIs.
- **Truncation in captures**: long JSON may be cut mid-string in some tools;
  invalid JSON at the tail means the logical object is incomplete.

### 2.2 Endpoints that constrain game interpretation

| Method + path pattern | Request (shape) | Response (shape) | Meaning for reconstruction |
| --- | --- | --- | --- |
| `POST /replaypoker/api/v2/tokens` | Body includes client/table context (exact keys vary by client version) | `{ token: "<jwt>", ... }` | JWT for WebSocket `?token=`; without it, no live `table:*` topic. |
| `GET /replaypoker/api/v2/users?ids=<id>[,...]&table_id=<id>` | Query: comma-separated user ids + table | Array or map of **user** objects: display name, rank, level, locale, avatar URL, etc. | Maps `userId` in WS payloads to human-readable identity. |
| `GET /replaypoker/api/v2/settings` | — | User preference blob: table options (`autoMuck`, `chatFilter`, …) | Explains behaviour toggles echoed later in WS `seat` / `setSeatOption`. |
| `GET /replaypoker/api/v2/missions?...` | Query filters | Mission list + progress | Context only; not required per hand. |
| `GET /replaypoker/api/v2/tables/<tableId>/sit_requirements` | — | Buy-in min/max, current bankroll, seat rules | Interpreting sit/stand and max buy. |

### 2.3 Low-signal endpoints (context, not hand history)

These are useful for **product** understanding but rarely change interpretation
of a single hand:

- `GET /replaypoker/api/v2/friends` — often empty `[]` in samples; social layer.
- `GET /replaypoker/api/v2/hidden_avatars` — UI preference.
- `POST /replaypoker/api/iterableProxy`, `POST /replaypoker/api/iterableToken` —
  push-notification vendor plumbing.
- `GET /replaypoker/lobby/(rings|sngs|mtts|missions)?...` — large
  **React Server Component** payloads (lobby list inside UI tree); table listing
  is easier from DOM or dedicated JSON elsewhere.

---

## 3. WebSocket wire format (Phoenix)

### 3.1 Frame text

The WebSocket **application message** is UTF-8 text. In practice each gameplay
message is a **single JSON array** of exactly **five** elements:

```text
[ joinRef, ref, topic, event, payload ]
```

| Index | Name | JSON type | Constraints | Example | Meaning |
| --- | --- | --- | --- | --- | --- |
| 0 | `joinRef` | string, number, or `null` | Correlates to the channel join | `"7"` | Phoenix join reference; sent on joined channel messages. |
| 1 | `ref` | string, number, or `null` | Request/response correlation | `"9"` or `null` | Non-null often means a reply to a client push. |
| 2 | `topic` | string | `table:<numericTableId>` for play | `"table:16809114"` | Identifies which table channel. |
| 3 | `event` | string | See §3.2 | `"output"` | Discriminates message role. |
| 4 | `payload` | object, array, or `null` | Shape depends on `event` | `{ "updates": [...] }` | Event body. |

**Parsing rule**: if element 2 is not a string or element 3 is not a string,
this is not a Phoenix v2 channel frame in the usual form (e.g. heartbeats or
other wrappers).

### 3.2 `event` values (game-relevant subset)

| `event` | Typical direction | Payload summary |
| --- | --- | --- |
| `state` | server → client | Full table snapshot: seats, blinds, current hand, settings. |
| `output` | server → client | `{ updates: Update[], handId?, requestId?, ... }` — delta log. |
| `input` | client → server | `{ action: string, ... }` — player/client command. |
| `broadcast` | server → client | `{ broadcast: string, ... }` — room broadcast discriminator. |
| `updates` | client → server | `{ handId, sequence }` — resume / catch-up (§4.5). |
| `phx_*` | either | Channel lifecycle (join, reply, error, leave). **Payloads are operational**, not card/pot facts; still useful for ordering joins vs first `state`. |

### 3.3 `output` payload — top-level keys

| Key | Type | Constraints | Meaning |
| --- | --- | --- | --- |
| `updates` | `array` | Required for gameplay; may be empty | Ordered list of state mutations. |
| `handId` | string (opaque) | Present when a hand is active or referenced | Identifies the current hand across frames. |
| `requestId` | string (opaque) | Optional | Server-side correlation; safe to ignore for rules. |

Each element of `updates` is an **object**; the mandatory discriminator is:

| Key | Type | Meaning |
| --- | --- | --- |
| `action` | string | Names what this update describes (§4.2). |

Other keys on the update object depend on `action` (seats, chips, cards, etc.).

### 3.4 `state` snapshot — conceptual top-level keys

Exact schemas drift with client/server versions. For interpretation, expect
**at least** the following **concepts** (names inferred from traffic; always
verify against your capture):

| Concept | Typical key(s) | Meaning |
| --- | --- | --- |
| Seats | `seats` | Per-seat occupancy, `userId`, stack, status (`reserved`, `playing`, `sitOut`, `available`, …). |
| Blinds / stakes | `blinds`, `smallBlind`, `bigBlind` | Limit structure for the table. |
| Dealer button | `dealer`, `buttonSeat` | Which seat holds the button. |
| Current hand | `currentHand` | Nested object: stage, board, pots, actors, timers. |
| Table id | inside topic / payload | Must match URL `/table/<id>`. |

Treat `state` as **authoritative ground truth** at the instant it arrives;
subsequent `output` updates apply **on top** of the last known state.

---

## 4. Semantics: `updates[].action` and related fields

### 4.1 Authoritative vs non-authoritative updates

- **Authoritative** — changes chips, cards, pots, seat occupancy, or records a
  committed **street action** (fold/call/raise…).
- **Informational / UI** — timers, connection lists, dealer text, intent
  previews, duplicate seat echoes for the acting user only.

Section 6 lists **structural keys** that distinguish these cases without numeric
ranking heuristics.

### 4.2 `output` — `updates[]` action catalogue

Unless noted, each row is **authoritative** for reconstruction when present.

| `action` | Typical fields (non-exhaustive) | Meaning |
| --- | --- | --- |
| `startHand` | `handId`, seat list, `dealer`, antes/blinds setup | New hand; participants and button. |
| `blinds` | `seatId`, amounts, `players` chips snapshot | Small/large blind (and dead money) posted. |
| `dealHoleCards` | `players[]` with `seatId`, `cards` | Hole cards; opponents often `["X","X"]` (§5). |
| `dealCommunityCards` | `cards` (0–5 strings), `street`, `players` with `handStrength` | Flop/turn/river; updates shown strengths. |
| `check`, `call`, `bet`, `raise`, `fold`, `allIn` | `seatId`, `chips` deltas, `players` snapshot | Committed action; always tie to `seatId`. |
| `updatePots` | `pots[]`, `rake`, totals | Pot structure after betting round. |
| `betRefund` | `seatId`, amount | Uncalled bet returned. |
| `showdown` | pot index / seat references | Marks entry to showdown phase for a pot. |
| `show` | hole cards revealed, `handStrength` | Player shows at showdown. |
| `muck` | `seatId` | Player does not show. |
| `awardPot` | winners, amounts, `handStrength` | Chips awarded; final evaluated hands. |
| `finishHand` | per-seat stacks | Hand complete; baseline for next `startHand`. |
| `seat` | `seatId`, `status`, `sequence`, `userId`, `stack`, … | **If** `routeToUser === true`: echo of **preference** only (sit-out-next-hand, auto-muck, stand). **If** no `routeToUser` and includes **`sequence`**: real seat/state transition. |

**Non-authoritative** `action` values (retain only if you care about timers / UX):

| `action` | Why it is not a game step |
| --- | --- |
| `tick` | Turn timer; carries `timeoutAt` only. |
| `extendedConnections` | List of connected `userId`s (presence). |
| `dealerMessage` | Dealer chat / rule reminder string. |
| `resetTable` | Between-hands cleanup; often duplicates `finishHand` info. |
| `intent` | Another player’s *planned* action preview, not a commitment. |

**Frame-level rule**: An `output` frame is worth keeping if **any** `updates[]`
entry is authoritative. If **every** entry is non-authoritative, the frame adds
no facts beyond timers/presence.

### 4.3 `input` — client actions

Payload always includes:

| Key | Type | Meaning |
| --- | --- | --- |
| `action` | string | Verb mirrored server-side in `output` when accepted. |

| `action` | Authoritative when committed? | Notes |
| --- | --- | --- |
| `sit`, `stand` | Yes | Seating changes (subject to server rules). |
| `call`, `bet`, `raise`, `fold`, `check`, `allIn` | Yes | Your play; should mirror into `output`. |
| `setIntent`, `clearIntent` | No | Planning aid only. |
| `setSeatOption` | No | Toggles preferences (`autoMuck`, `sitOutNextHand`, …). |
| `ping` | No | Keepalive. |
| *(event name)* `updates` on channel | N/A | Not `payload.action`; this is the **event** name for resume (§4.5). |

### 4.4 `broadcast`

Payload shape:

| Key | Type | Meaning |
| --- | --- | --- |
| `broadcast` | string | Event kind (e.g. tournament notices). |

Additional keys depend on `broadcast`. These events **do not** replace `output`
for card/pot truth, but they **do** affect tournament or room context.

### 4.5 Client resume pseudo-action

When the **event** name is `updates` (not `updates[]` inside `output`) and the
payload is only:

```json
{ "handId": "<id>", "sequence": <number> }
```

this is a **sync request**, not a game mutation. Use it to detect reconnects,
not to infer new cards or bets.

---

## 5. Card, seat, and evaluation encoding

### 5.1 Card strings

- **Visible cards**: two-character strings: **rank** + **suit**.
  - Ranks: `2`–`9`, `T`, `J`, `Q`, `K`, `A`.
  - Suits: `c` clubs, `d` diamonds, `h` hearts, `s` spades.
  - Example: `Ah` = ace of hearts; `Td` = ten of diamonds.
- **Hidden / unknown hole cards**: often the literal `"X"` per card, e.g.
  `["X","X"]` until showdown or for opponents.

### 5.2 Seat model

- **`seatId`**: integer (or numeric string in JSON) identifying a physical seat.
- **Button / blinds**: given relative to `seatId` in `startHand` / `blinds`.
- **Statuses** (examples; server is canonical): `reserved`, `playing`, `sitOut`,
  `available` — interpret together with `userId` presence and `stack`.

### 5.3 `handStrength` (typical shape)

When present on a player object, `handStrength` summarizes the best five-card
hand. Exact fields vary; common patterns include:

- **Category name**: e.g. pair, flush, straight.
- **Comparator / rank**: numeric or structured value used to break ties.
- **Cards used**: subset of hole + board contributing to the strength.

Use `handStrength` as **display-evaluated truth** from the server, not as
something to recompute unless you are validating the client.

### 5.4 Chips and pots

- **`chips`**: integer counts in table currency (same unit as buy-in on REST).
- **`pots[]`**: may include main + side pots with eligibility per seat.
- **`rake`**: house take; appears in `updatePots` / award summaries in ring games.

---

## 6. Separating hand facts from session chatter (key-based rules)

Use **stable structural keys** only — no numeric scoring.

### 6.1 Drop an `output` frame entirely when

- `payload.updates` is missing, empty, or every element matches §4.2
  **non-authoritative** list (`tick`, `extendedConnections`, `dealerMessage`,
  `resetTable`, `intent`), **or**
- every `seat` update has **`routeToUser: true`** (preference echoes only).

### 6.2 Keep an `output` frame when

- Any update has `action` in:
  `startHand`, `blinds`, `dealHoleCards`, `dealCommunityCards`,
  `check`, `call`, `bet`, `raise`, `fold`, `allIn`,
  `updatePots`, `betRefund`, `showdown`, `show`, `muck`, `awardPot`, `finishHand`,
  **or**
- `action === "seat"` **and** `routeToUser` is absent/false **and** a
  **`sequence`** field is present (state transition).

### 6.3 `input` frames — keep when

- `payload.action` ∈ { `sit`, `stand`, `call`, `bet`, `raise`, `fold`, `check`, `allIn` }.

Drop `input` when `payload.action` ∈ { `setIntent`, `clearIntent`,
`setSeatOption`, `ping` }.

### 6.4 Worked micro-examples

**Example A — drop**

```json
{ "updates": [
  { "action": "tick", "timeoutAt": 1778852145000 },
  { "action": "extendedConnections", "userIds": [4558098, 4558099] }
]}
```

No committed action, no new cards/chips; only timer + presence.

**Example B — keep**

```json
{ "updates": [
  { "action": "tick", "timeoutAt": 1778852145000 },
  { "action": "bet", "seatId": 3, "chips": 200, "players": [ ... ] }
]}
```

Contains `bet` → authoritative.

**Example C — `seat` ambiguity**

```json
{ "action": "seat", "routeToUser": true, "autoMuck": true }
```

Preference echo → treat as chatter for hand history.

```json
{ "action": "seat", "seatId": 2, "status": "sitOut", "sequence": 4182 }
```

Seat state change → keep.

---

## 7. Line-oriented archive / export layout

Many workflows paste captures into a **single text file** sorted **oldest →
newest** (time order). The following grammar matches a common exporter shape;
the **opening title line is capture metadata only** (not from Replay Poker
servers). Line 1 is any `=== … ===` banner you can ignore for protocol parsing.

### 7.1 File header

Block shape (blank line after `search:` is part of the format):

```text
=== <export banner — capture metadata, not game data> ===
exported: <ISO-8601>
count: <N> meaningful message(s)
total-in-storage: <M>
kinds: <comma-separated lowercase kinds, lexicographic order; e.g. beacon, fetch, postmessage, sse, ws, xhr>
search: "<filter substring>" | search: (none)

```

- **`meaningful message(s)`** — exporter jargon: rows that passed a local
  “keep for analysis” filter, not a server term.
- **`total-in-storage`** — size of the full buffer before export-time kind /
  search filtering.
- **`kinds`** — which transport kinds were included when building the file
  (checkbox / filter set).
- **`search`** — case-insensitive substring filter applied to the export; if no
  filter was set, the literal `search: (none)`.

If nothing matched, the file may end immediately after the header with:

```text
(no messages match the current filter)
```

### 7.2 Per-message record

Records are separated by **one blank line**. Each record begins with a
delimiter line, then optional metadata lines, then optional body sections.

**Delimiter line** — join these tokens with **two spaces** between each.
If `<method-or-phase>` is empty, it may be omitted entirely (so two
consecutive spaces never appear for an empty token).

```text
--- #<index>  <ISO-8601 or (no-ts)>  <KIND>  <method-or-phase>  <verdict>/<category>  score=<integer> ---
```

| Token | Meaning |
| --- | --- |
| `#<index>` | 1-based sequence in the export file |
| `<KIND>` | Uppercase kind: `FETCH`, `XHR`, `WS`, `SSE`, `BEACON`, `POSTMESSAGE`, … |
| `<method-or-phase>` | HTTP method (`GET`, `POST`, …) for fetch/xhr; WebSocket phase (`recv`, `send`, `open`, …) for `WS` |
| `<verdict>/<category>` | Optional labels attached by the recording pipeline (not server fields); `?` if missing |
| `score=` | **Omit from protocol replay** — capture-side hint only, not server data |

**Optional lines** (each emitted only when the value exists; order as below):

```text
url: <full URL>
status: <code> <optional statusText>
error: <text>
page: <browser location at capture>
socket-event: <derived sub-event name, if any>
matched-keys: <comma-separated JSON key names>
top-level-keys: <comma-separated JSON key names>
reasons: <semicolon-separated pipeline notes>
```

Then a **blank line**, then one or more body blocks:

**Body blocks** — for `fetch` / `xhr`, when request and/or response bodies exist:

```text
request-body [json|text|formdata|urlencoded blob|binary]:
<body text>

response-body [json|text|...]:
<body text>
```

For `ws`, `sse`, `beacon`, `postmessage` — when payload exists:

```text
data [json|text|...]:
<body text>
```

If multiple body blocks apply, they are concatenated with newlines (e.g. both
request and response for XHR).

**Interpreting bodies for poker**: the WebSocket `data` line should contain the
raw five-element Phoenix JSON array as captured on the wire.

---

## 8. Reconstruction cookbook (one hand)

1. **Anchor the table** — From the play URL or `state` topic, read `tableId`.
2. **Hydrate identities** — Merge `userId` from WS with
   `GET .../api/v2/users?ids=...` for names and avatars.
3. **Establish baseline** — Apply the latest `state` before the hand’s
   `startHand` (or the `finishHand` of the prior hand).
4. **Slice updates** — Collect consecutive `output` frames with the **same**
   authoritative `handId` (from payload or embedded update fields).
5. **Deal timeline**
   - `startHand` → participants + button.
   - `blinds` → forced bets.
   - `dealHoleCards` → per-seat private cards (masking as `X` until known).
   - `dealCommunityCards` → board by street.
6. **Betting** — Fold/call/bet/raise/all-in/check in seat order; after each
   street, `updatePots` reconciles pot geometry.
7. **Showdown** — `showdown`, then `show` / `muck`, then `awardPot`.
8. **Close** — `finishHand` gives final stacks; next `startHand` begins a new
   logical hand **even if** `handId` increments off-screen between your retained
   frames.

If a capture **starts mid-hand**, wait for the first full `state` or infer
partial state from `dealCommunityCards` + stack sizes, knowing preflop may be
incomplete.

---

## 9. Protocol gaps and forward compatibility

- **Unknown `action` strings** — Preserve them; the server may add new steps.
- **`broadcast` variants** — Treat `broadcast` as an open enumeration.
- **Field renames** — Prefer **semantic** matching (cards, pots, seats) over
  exact key spellings across years of client deploys.
- **Partial captures** — Missing `state` or early `startHand` may leave hole
  cards ambiguous until a later `show` / `awardPot`.

---

*End of reference.*
