docs: comprehensive API documentation from source code
Replace existing docs with fresh documentation built entirely from source code analysis. OpenAPI 3.1 spec as source of truth, plus human-readable Markdown with curl examples, response samples, and workflow guides. - OpenAPI 3.1 spec covering all 42 endpoints (validated against source) - 7 endpoint reference docs (auth, games, sessions, picker, stats, votes, webhooks) - WebSocket protocol documentation (auth, subscriptions, 4 event types) - 4 guide documents (getting started, session lifecycle, voting, webhooks) - API README with overview, auth docs, and quick reference table - Old docs archived to docs/archive/ Made-with: Cursor
This commit is contained in:
316
docs/api/guides/getting-started.md
Normal file
316
docs/api/guides/getting-started.md
Normal file
@@ -0,0 +1,316 @@
|
||||
# Getting Started
|
||||
|
||||
A narrative walkthrough of the minimum viable integration path. Use this guide to go from zero to a completed game night session using the Jackbox Game Picker API.
|
||||
|
||||
**Prerequisites:** API running locally (`http://localhost:5000`), admin key set via `ADMIN_KEY` environment variable.
|
||||
|
||||
---
|
||||
|
||||
## 1. Health Check
|
||||
|
||||
Verify the API is running before anything else. The health endpoint requires no authentication.
|
||||
|
||||
**Why:** Quick sanity check. If this fails, nothing else will work.
|
||||
|
||||
```bash
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"message": "Jackbox Game Picker API is running"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Authenticate
|
||||
|
||||
Exchange your admin key for a JWT. You'll use this token for all write operations (creating sessions, adding games, closing sessions).
|
||||
|
||||
**Why:** Creating sessions, adding games to them, and closing sessions require authentication. The picker and game listings are public, but session management is not.
|
||||
|
||||
See [Auth endpoints](../endpoints/auth.md) for full details.
|
||||
|
||||
```bash
|
||||
TOKEN=$(curl -s -X POST http://localhost:5000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "your-admin-key"}' | jq -r '.token')
|
||||
```
|
||||
|
||||
Or capture the full response:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "your-admin-key"}'
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJ0aW1lc3RhbXAiOjE3MTAwMDAwMDAwLCJpYXQiOjE3MTAwMDAwMDB9.abc123",
|
||||
"message": "Authentication successful",
|
||||
"expiresIn": "24h"
|
||||
}
|
||||
```
|
||||
|
||||
Store `token` in `$TOKEN` for the remaining steps. Tokens expire after 24 hours.
|
||||
|
||||
---
|
||||
|
||||
## 3. Browse Games
|
||||
|
||||
List available games. Use query parameters to narrow the catalog—for example, `playerCount` filters to games that support that many players.
|
||||
|
||||
**Why:** Know what's in the catalog before you pick. Filtering by player count ensures you only see games you can actually play.
|
||||
|
||||
See [Games endpoints](../endpoints/games.md) for all filters.
|
||||
|
||||
```bash
|
||||
curl "http://localhost:5000/api/games?playerCount=6"
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"pack_name": "Jackbox Party Pack 7",
|
||||
"title": "Quiplash 3",
|
||||
"min_players": 3,
|
||||
"max_players": 8,
|
||||
"length_minutes": 15,
|
||||
"has_audience": 1,
|
||||
"family_friendly": 0,
|
||||
"game_type": "Writing",
|
||||
"secondary_type": null,
|
||||
"play_count": 0,
|
||||
"popularity_score": 0,
|
||||
"enabled": 1,
|
||||
"favor_bias": 0,
|
||||
"created_at": "2024-01-15T12:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"pack_name": "Jackbox Party Pack 7",
|
||||
"title": "The Devils and the Details",
|
||||
"min_players": 3,
|
||||
"max_players": 7,
|
||||
"length_minutes": 25,
|
||||
"has_audience": 1,
|
||||
"family_friendly": 0,
|
||||
"game_type": "Strategy",
|
||||
"secondary_type": null,
|
||||
"play_count": 0,
|
||||
"popularity_score": 0,
|
||||
"enabled": 1,
|
||||
"favor_bias": 0,
|
||||
"created_at": "2024-01-15T12:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Pick a Game
|
||||
|
||||
Get a weighted random game based on your filters. The picker considers favor/disfavor bias and can avoid recently played games when a session is provided.
|
||||
|
||||
**Why:** Instead of manually choosing, let the API pick a game that fits your player count, length, and other preferences. Use the same filters you used to browse.
|
||||
|
||||
See [Picker endpoint](../endpoints/picker.md) for all options.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/pick \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"playerCount": 6}'
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"game": {
|
||||
"id": 1,
|
||||
"pack_name": "Jackbox Party Pack 7",
|
||||
"title": "Quiplash 3",
|
||||
"min_players": 3,
|
||||
"max_players": 8,
|
||||
"length_minutes": 15,
|
||||
"has_audience": 1,
|
||||
"family_friendly": 0,
|
||||
"game_type": "Writing",
|
||||
"secondary_type": null,
|
||||
"play_count": 0,
|
||||
"popularity_score": 0,
|
||||
"enabled": 1,
|
||||
"favor_bias": 0,
|
||||
"created_at": "2024-01-15T12:00:00.000Z"
|
||||
},
|
||||
"poolSize": 12,
|
||||
"totalEnabled": 17
|
||||
}
|
||||
```
|
||||
|
||||
Save the `game.id` (e.g. `1`) — you'll use it when adding the game to the session.
|
||||
|
||||
---
|
||||
|
||||
## 5. Start a Session
|
||||
|
||||
Create a new gaming session. Only one session can be active at a time. Use notes to label the night (e.g., "Friday game night").
|
||||
|
||||
**Why:** Sessions track which games you played, when, and support voting and room monitoring. Starting a session marks the beginning of your game night.
|
||||
|
||||
See [Sessions endpoints](../endpoints/sessions.md) for full details.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/sessions \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Friday game night"}'
|
||||
```
|
||||
|
||||
**Sample response (201 Created):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
"notes": "Friday game night",
|
||||
"is_active": 1,
|
||||
"created_at": "2026-03-15T19:00:00.000Z",
|
||||
"closed_at": null
|
||||
}
|
||||
```
|
||||
|
||||
Save the `id` (e.g. `5`) — you'll use it to add games and close the session.
|
||||
|
||||
---
|
||||
|
||||
## 6. Add the Picked Game
|
||||
|
||||
Add the game you picked (step 4) to the session you created (step 5). You can optionally pass a room code once the game is running.
|
||||
|
||||
**Why:** Adding a game to the session records that you played it, increments play counts, and enables voting and room monitoring. Use `game_id` from the pick response.
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"game_id": 1, "room_code": "ABCD"}'
|
||||
```
|
||||
|
||||
Replace `5` with your session ID and `1` with the `game.id` from the pick response.
|
||||
|
||||
**Sample response (201 Created):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 14,
|
||||
"session_id": 5,
|
||||
"game_id": 1,
|
||||
"manually_added": 0,
|
||||
"status": "playing",
|
||||
"room_code": "ABCD",
|
||||
"played_at": "2026-03-15T20:30:00.000Z",
|
||||
"pack_name": "Jackbox Party Pack 7",
|
||||
"title": "Quiplash 3",
|
||||
"game_type": "Writing",
|
||||
"min_players": 3,
|
||||
"max_players": 8
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Close the Session
|
||||
|
||||
When the game night is over, close the session. Any games still marked `playing` are automatically marked `played`.
|
||||
|
||||
**Why:** Closing the session finalizes it, frees the "active session" slot for the next night, and triggers any end-of-session webhooks or WebSocket events.
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/close" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Great session!"}'
|
||||
```
|
||||
|
||||
Replace `5` with your session ID.
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
"notes": "Great session!",
|
||||
"is_active": 0,
|
||||
"created_at": "2026-03-15T19:00:00.000Z",
|
||||
"closed_at": "2026-03-15T23:30:00.000Z",
|
||||
"games_played": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Step | Endpoint | Auth |
|
||||
|------|----------|------|
|
||||
| 1 | `GET /health` | No |
|
||||
| 2 | `POST /api/auth/login` | No |
|
||||
| 3 | `GET /api/games?playerCount=6` | No |
|
||||
| 4 | `POST /api/pick` | No |
|
||||
| 5 | `POST /api/sessions` | Bearer |
|
||||
| 6 | `POST /api/sessions/{id}/games` | Bearer |
|
||||
| 7 | `POST /api/sessions/{id}/close` | Bearer |
|
||||
|
||||
---
|
||||
|
||||
## Full Copy-Paste Flow
|
||||
|
||||
```bash
|
||||
# 1. Health check
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# 2. Get token (replace with your actual admin key)
|
||||
TOKEN=$(curl -s -X POST http://localhost:5000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key": "your-admin-key"}' | jq -r '.token')
|
||||
|
||||
# 3. Browse games for 6 players
|
||||
curl "http://localhost:5000/api/games?playerCount=6"
|
||||
|
||||
# 4. Pick a game for 6 players
|
||||
PICK=$(curl -s -X POST http://localhost:5000/api/pick \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"playerCount": 6}')
|
||||
GAME_ID=$(echo $PICK | jq -r '.game.id')
|
||||
|
||||
# 5. Start session
|
||||
SESSION=$(curl -s -X POST http://localhost:5000/api/sessions \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Friday game night"}')
|
||||
SESSION_ID=$(echo $SESSION | jq -r '.id')
|
||||
|
||||
# 6. Add picked game to session
|
||||
curl -X POST "http://localhost:5000/api/sessions/$SESSION_ID/games" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"game_id\": $GAME_ID, \"room_code\": \"ABCD\"}"
|
||||
|
||||
# 7. Close session when done
|
||||
curl -X POST "http://localhost:5000/api/sessions/$SESSION_ID/close" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Great session!"}'
|
||||
```
|
||||
|
||||
This assumes `jq` is installed for JSON parsing. Without it, extract IDs manually from the JSON responses.
|
||||
287
docs/api/guides/session-lifecycle.md
Normal file
287
docs/api/guides/session-lifecycle.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Session Lifecycle Guide
|
||||
|
||||
This guide walks through the full lifecycle of a Jackbox gaming session—from creation through closing and deletion—with narrative explanations, behavior notes, and curl examples.
|
||||
|
||||
**Base URL:** `http://localhost:5000`
|
||||
**Authentication:** All write operations require a Bearer token. Set `TOKEN` in your shell and use `-H "Authorization: Bearer $TOKEN"` in curl examples.
|
||||
|
||||
---
|
||||
|
||||
## 1. Creating a Session
|
||||
|
||||
Only **one active session** can exist at a time. If an active session already exists, you must close it before creating a new one.
|
||||
|
||||
Notes are optional; they help you remember what a session was for (e.g., "Friday game night", "Birthday party").
|
||||
|
||||
Creating a session triggers a **`session.started`** WebSocket event broadcast to all authenticated clients. See [Real-time updates via WebSocket](#9-real-time-updates-via-websocket) for details.
|
||||
|
||||
**Endpoint:** [POST /api/sessions](../endpoints/sessions.md#post-apisessions)
|
||||
|
||||
```bash
|
||||
# Create a session with notes
|
||||
curl -X POST "http://localhost:5000/api/sessions" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Friday game night"}'
|
||||
|
||||
# Create a session without notes (body can be empty)
|
||||
curl -X POST "http://localhost:5000/api/sessions" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
**Response (201 Created):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
"notes": "Friday game night",
|
||||
"is_active": 1,
|
||||
"created_at": "2026-03-15T19:00:00.000Z",
|
||||
"closed_at": null
|
||||
}
|
||||
```
|
||||
|
||||
If an active session already exists, you receive `400` with a message like `"An active session already exists. Please close it before creating a new one."` and an `activeSessionId` in the response.
|
||||
|
||||
---
|
||||
|
||||
## 2. Adding Games
|
||||
|
||||
You can add games in two ways: via the **picker** (weighted random selection) or **manually** by specifying a game ID.
|
||||
|
||||
### Via the Picker
|
||||
|
||||
First, use [POST /api/pick](../endpoints/picker.md#post-apipick) to select a game with filters and repeat avoidance. Then add that game to the session.
|
||||
|
||||
```bash
|
||||
# 1. Pick a game (optionally filter by player count, session for repeat avoidance)
|
||||
GAME=$(curl -s -X POST "http://localhost:5000/api/pick" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"playerCount": 6, "sessionId": 5}' | jq -r '.game.id')
|
||||
|
||||
# 2. Add the picked game to the session
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"game_id\": $GAME, \"manually_added\": false}"
|
||||
```
|
||||
|
||||
### Manual Addition
|
||||
|
||||
Add a game directly by its `game_id` (from the games catalog):
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"game_id": 42, "manually_added": true, "room_code": "ABCD"}'
|
||||
```
|
||||
|
||||
**Endpoint:** [POST /api/sessions/{id}/games](../endpoints/sessions.md#post-apisessionsidgames)
|
||||
|
||||
### Side Effects of Adding a Game
|
||||
|
||||
When you add a game to an active session, several things happen automatically:
|
||||
|
||||
1. **Previous `playing` games** are auto-transitioned to **`played`**. At most one game is `playing` at a time.
|
||||
2. The game's **`play_count`** is incremented in the catalog.
|
||||
3. The **`game.added`** webhook is fired (if you have webhooks configured) and a **`game.added`** WebSocket event is broadcast to session subscribers.
|
||||
4. If you provide a **`room_code`**, the room monitor is **auto-started** for player count tracking.
|
||||
|
||||
Newly added games start with status **`playing`**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Tracking Game Status
|
||||
|
||||
Each game in a session has a status: **`playing`**, **`played`**, or **`skipped`**.
|
||||
|
||||
| Status | Meaning |
|
||||
|----------|-------------------------------------------|
|
||||
| `playing`| Currently being played (at most one at a time) |
|
||||
| `played` | Finished playing |
|
||||
| `skipped`| Skipped (e.g., technical issues); stays skipped |
|
||||
|
||||
**Behavior:** When you change a game's status to **`playing`**, any other games with status `playing` are automatically set to **`played`**. Skipped games are never auto-transitioned; they remain `skipped`.
|
||||
|
||||
**Endpoint:** [PATCH /api/sessions/{sessionId}/games/{sessionGameId}/status](../endpoints/sessions.md#patch-apisessionssessionidgamessessiongameidstatus)
|
||||
|
||||
**Important:** In session game sub-routes, `sessionGameId` refers to **`session_games.id`** (the row in the `session_games` table), **not** `games.id`. When listing session games with `GET /api/sessions/{id}/games`, the `id` field in each object is the `session_games.id`.
|
||||
|
||||
```bash
|
||||
# Mark a game as played (sessionGameId 14, not game_id)
|
||||
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "played"}'
|
||||
|
||||
# Mark a game as playing (others playing → played)
|
||||
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "playing"}'
|
||||
|
||||
# Mark a game as skipped
|
||||
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "skipped"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Room Codes
|
||||
|
||||
Room codes are 4-character strings used by Jackbox games for lobby entry. Valid format: exactly 4 characters, uppercase letters (A–Z) and digits (0–9) only. Example: `ABCD`, `XY9Z`.
|
||||
|
||||
A room code enables **room monitoring** for player count. You can set or update it when adding a game or via a dedicated PATCH endpoint.
|
||||
|
||||
**Endpoint:** [PATCH /api/sessions/{sessionId}/games/{sessionGameId}/room-code](../endpoints/sessions.md#patch-apisessionssessionidgamessessiongameidroom-code)
|
||||
|
||||
```bash
|
||||
# Set room code when adding a game
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"game_id": 42, "room_code": "ABCD"}'
|
||||
|
||||
# Update room code later
|
||||
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/room-code" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"room_code": "XY9Z"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Player Count Monitoring
|
||||
|
||||
For games with a room code, you can track how many players join. The room monitor polls the Jackbox lobby to detect player count changes.
|
||||
|
||||
- **Start monitoring:** [POST /api/sessions/{sessionId}/games/{sessionGameId}/start-player-check](../endpoints/sessions.md#post-apisessionssessionidgamessessiongameidstart-player-check)
|
||||
- **Stop monitoring:** [POST /api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check](../endpoints/sessions.md#post-apisessionssessionidgamessessiongameidstop-player-check)
|
||||
- **Manual update:** [PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count](../endpoints/sessions.md#patch-apisessionssessionidgamessessiongameidplayer-count)
|
||||
|
||||
When the player count changes (via room monitor or manual update), a **`player-count.updated`** WebSocket event is broadcast to session subscribers.
|
||||
|
||||
```bash
|
||||
# Start room monitor (game must have a room code)
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games/14/start-player-check" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Manually set player count
|
||||
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/player-count" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"player_count": 6}'
|
||||
|
||||
# Stop monitoring
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/games/14/stop-player-check" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Closing Sessions
|
||||
|
||||
Closing a session marks it as inactive. The API:
|
||||
|
||||
1. Auto-finalizes all games with status **`playing`** to **`played`**
|
||||
2. Sets `closed_at` and `is_active = 0`
|
||||
3. Triggers a **`session.ended`** WebSocket broadcast to session subscribers
|
||||
|
||||
You can add or update session notes in the close request body.
|
||||
|
||||
**Endpoint:** [POST /api/sessions/{id}/close](../endpoints/sessions.md#post-apisessionsidclose)
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/close" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Great session!"}'
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
"notes": "Great session!",
|
||||
"is_active": 0,
|
||||
"created_at": "2026-03-15T19:00:00.000Z",
|
||||
"closed_at": "2026-03-15T23:30:00.000Z",
|
||||
"games_played": 4
|
||||
}
|
||||
```
|
||||
|
||||
You cannot add games to a closed session.
|
||||
|
||||
---
|
||||
|
||||
## 7. Exporting Session Data
|
||||
|
||||
Export a session in two formats: **JSON** (structured) or **TXT** (human-readable).
|
||||
|
||||
**Endpoint:** [GET /api/sessions/{id}/export](../endpoints/sessions.md#get-apisessionsidexport)
|
||||
|
||||
- **JSON** (`?format=json`): Includes `session`, `games`, and `chat_logs` as structured data. Useful for archival or integrations.
|
||||
- **TXT** (default): Human-readable plaintext with headers and sections.
|
||||
|
||||
```bash
|
||||
# Export as JSON
|
||||
curl -o session-5.json "http://localhost:5000/api/sessions/5/export?format=json" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Export as TXT (default)
|
||||
curl -o session-5.txt "http://localhost:5000/api/sessions/5/export" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Deleting Sessions
|
||||
|
||||
Sessions must be **closed** before deletion. Active sessions cannot be deleted.
|
||||
|
||||
Deletion **cascades** to related data:
|
||||
|
||||
- `session_games` rows are deleted
|
||||
- `chat_logs` rows are deleted
|
||||
|
||||
**Endpoint:** [DELETE /api/sessions/{id}](../endpoints/sessions.md#delete-apisessionsid)
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:5000/api/sessions/5" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Session deleted successfully",
|
||||
"sessionId": 5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Real-time Updates via WebSocket
|
||||
|
||||
The API provides real-time updates over WebSocket for session events: `session.started`, `game.added`, `session.ended`, and `player-count.updated`. Connect to `/api/sessions/live`, authenticate with your JWT, and subscribe to session IDs to receive these events without polling.
|
||||
|
||||
For connection setup, message types, and event payloads, see [WebSocket Protocol](../websocket.md).
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: sessionGameId vs game_id
|
||||
|
||||
| Context | ID meaning | Example |
|
||||
|---------|------------|---------|
|
||||
| `POST /api/sessions/{id}/games` body | `game_id` = catalog `games.id` | `{"game_id": 42}` |
|
||||
| `GET /api/sessions/{id}/games` response `id` | `session_games.id` | Use `14` in sub-routes |
|
||||
| `PATCH .../games/{sessionGameId}/status` | `sessionGameId` = `session_games.id` | `/sessions/5/games/14/status` |
|
||||
|
||||
When in doubt: session game sub-routes use **`session_games.id`**, not `games.id`.
|
||||
207
docs/api/guides/voting-and-popularity.md
Normal file
207
docs/api/guides/voting-and-popularity.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Voting and Popularity
|
||||
|
||||
A narrative guide to how the Jackbox Game Picker handles community voting and game popularity. This system lets viewers and stream chat influence which games rise to the top—without directly controlling the random picker.
|
||||
|
||||
---
|
||||
|
||||
## 1. How Popularity Works
|
||||
|
||||
Every game has a **popularity score** stored in the database:
|
||||
|
||||
```
|
||||
popularity_score = upvotes - downvotes
|
||||
```
|
||||
|
||||
The score is computed from `upvotes` and `downvotes` and persisted per game. As votes accumulate across sessions, the score reflects community sentiment over time.
|
||||
|
||||
**Important:** Popularity is used for **rankings** (e.g., "top rated games" in stats) but **does not directly affect picker weights**. The random picker uses favor bias, not popularity, when selecting games.
|
||||
|
||||
---
|
||||
|
||||
## 2. Favor Bias vs Popularity
|
||||
|
||||
Two separate systems govern how games are treated:
|
||||
|
||||
| Aspect | **Favor Bias** | **Popularity** |
|
||||
|--------|----------------|----------------|
|
||||
| Who controls it | Admin (via API) | Community (via votes) |
|
||||
| Values | `-1` (disfavor), `0` (neutral), `1` (favor) | `upvotes - downvotes` (unbounded) |
|
||||
| Affects picker? | Yes — directly changes weights | No |
|
||||
| Purpose | Manual curation; push/penalize specific games | Community sentiment; rankings |
|
||||
|
||||
**Favor bias** affects picker probability directly. Setting `favor_bias` to `1` on a game boosts its weight; `-1` reduces it. See [Games favor endpoint](../endpoints/games.md#patch-apigamesidfavor) and [Picker weighted selection](../endpoints/picker.md#weighted-selection).
|
||||
|
||||
**Popularity** is driven entirely by viewer votes. It surfaces in stats (e.g., `topRatedGames`) and session game lists, but the picker does not read it. These systems are independent.
|
||||
|
||||
---
|
||||
|
||||
## 3. Two Voting Mechanisms
|
||||
|
||||
The API supports two ways to record votes: batch chat import (after the fact) and live votes (real-time from bots).
|
||||
|
||||
### Chat Import (Batch, After-the-Fact)
|
||||
|
||||
Collect Twitch or YouTube chat logs containing `thisgame++` (upvote) and `thisgame--` (downvote), then submit them in bulk.
|
||||
|
||||
**Flow:**
|
||||
1. Export chat logs with `username`, `message`, and `timestamp` for each message.
|
||||
2. Filter or pass messages; the API parses `thisgame++` and `thisgame--` from the `message` field.
|
||||
3. POST to `POST /api/sessions/{id}/chat-import` with a `chatData` array of `{ username, message, timestamp }`.
|
||||
4. The API matches each vote’s timestamp to the game that was playing at that time (using `played_at` intervals).
|
||||
5. Votes are deduplicated by SHA-256 hash of `username:message:timestamp`.
|
||||
6. Response includes `votesByGame` breakdown and `debug` info (e.g., session timeline, vote matches).
|
||||
|
||||
See [Sessions chat-import endpoint](../endpoints/sessions.md#post-apisessionsidchat-import).
|
||||
|
||||
### Live Votes (Real-Time, from Bots)
|
||||
|
||||
A bot sends individual votes during the stream. Each vote is processed immediately.
|
||||
|
||||
**Flow:**
|
||||
1. Bot detects `thisgame++` or `thisgame--` (or equivalent) in chat.
|
||||
2. Bot sends `POST /api/votes/live` with `{ username, vote, timestamp }`.
|
||||
3. `vote` must be `"up"` or `"down"`.
|
||||
4. `timestamp` must be ISO 8601 (e.g., `2026-03-15T20:30:00Z`).
|
||||
5. The API finds the active session and matches the vote timestamp to the game playing at that time.
|
||||
6. **Deduplication:** Votes from the same username within 1 second are rejected with `409 Conflict`.
|
||||
|
||||
See [Votes live endpoint](../endpoints/votes.md#post-apivoteslive).
|
||||
|
||||
---
|
||||
|
||||
## 4. Timestamp Matching Explained
|
||||
|
||||
Games in a session have a `played_at` timestamp. A vote’s timestamp determines which game it belongs to.
|
||||
|
||||
**Rule:** A vote belongs to the game whose `played_at` is the **most recent one before** the vote timestamp.
|
||||
|
||||
Example session timeline:
|
||||
|
||||
- Game A: `played_at` 20:00
|
||||
- Game B: `played_at` 20:15
|
||||
- Game C: `played_at` 20:30
|
||||
|
||||
- Vote at 20:10 → Game A (last `played_at` before 20:10)
|
||||
- Vote at 20:20 → Game B
|
||||
- Vote at 20:45 → Game C (last game in session; captures all votes after it started)
|
||||
|
||||
The **last game** in the session captures all votes that occur after its `played_at`.
|
||||
|
||||
---
|
||||
|
||||
## 5. How Stats Reflect Popularity
|
||||
|
||||
`GET /api/stats` returns aggregate statistics, including:
|
||||
|
||||
- **mostPlayedGames** — top 10 by `play_count` (games with `play_count` > 0).
|
||||
- **topRatedGames** — top 10 by `popularity_score` (games with `popularity_score` > 0).
|
||||
|
||||
Both are limited to the top 10 and exclude games with score/count ≤ 0. See [Stats endpoint](../endpoints/stats.md).
|
||||
|
||||
---
|
||||
|
||||
## 6. Example Requests
|
||||
|
||||
### Chat Import
|
||||
|
||||
Import a batch of chat messages for session `5`:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/sessions/5/chat-import" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"chatData": [
|
||||
{
|
||||
"username": "viewer1",
|
||||
"message": "thisgame++",
|
||||
"timestamp": "2026-03-15T20:30:00Z"
|
||||
},
|
||||
{
|
||||
"username": "viewer2",
|
||||
"message": "thisgame--",
|
||||
"timestamp": "2026-03-15T20:31:00Z"
|
||||
},
|
||||
{
|
||||
"username": "viewer3",
|
||||
"message": "thisgame++",
|
||||
"timestamp": "2026-03-15T20:32:00Z"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Chat log imported and processed successfully",
|
||||
"messagesImported": 3,
|
||||
"duplicatesSkipped": 0,
|
||||
"votesProcessed": 3,
|
||||
"votesByGame": {
|
||||
"42": {
|
||||
"title": "Quiplash 3",
|
||||
"upvotes": 2,
|
||||
"downvotes": 1
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"sessionGamesTimeline": [
|
||||
{
|
||||
"title": "Quiplash 3",
|
||||
"played_at": "2026-03-15T20:00:00.000Z",
|
||||
"played_at_ms": 1742068800000
|
||||
}
|
||||
],
|
||||
"voteMatches": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Live Vote
|
||||
|
||||
Submit a single live vote (requires active session):
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/votes/live" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "viewer123",
|
||||
"vote": "up",
|
||||
"timestamp": "2026-03-15T20:30:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
**Sample response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Vote recorded successfully",
|
||||
"session": { "id": 3, "games_played": 5 },
|
||||
"game": {
|
||||
"id": 42,
|
||||
"title": "Quiplash 3",
|
||||
"upvotes": 11,
|
||||
"downvotes": 2,
|
||||
"popularity_score": 9
|
||||
},
|
||||
"vote": {
|
||||
"username": "viewer123",
|
||||
"type": "up",
|
||||
"timestamp": "2026-03-15T20:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Sessions endpoints](../endpoints/sessions.md) — chat import, session games, `played_at`
|
||||
- [Votes endpoints](../endpoints/votes.md) — live votes, deduplication, errors
|
||||
- [Stats endpoints](../endpoints/stats.md) — `mostPlayedGames`, `topRatedGames`
|
||||
- [Picker endpoints](../endpoints/picker.md) — weighted selection, favor bias (no popularity)
|
||||
- [Games endpoints](../endpoints/games.md) — favor bias per game and pack
|
||||
216
docs/api/guides/webhooks-and-events.md
Normal file
216
docs/api/guides/webhooks-and-events.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Webhooks and Events
|
||||
|
||||
A narrative guide to the Jackbox Game Picker event notification system: webhooks (HTTP callbacks) and WebSocket (persistent real-time connections). Both deliver event data about session and game activity.
|
||||
|
||||
---
|
||||
|
||||
## 1. Two Notification Systems
|
||||
|
||||
The API offers two complementary ways to receive event notifications:
|
||||
|
||||
| System | Model | Best for |
|
||||
|--------|-------|----------|
|
||||
| **Webhooks** | HTTP POST callbacks to your URL | Server-to-server, external integrations |
|
||||
| **WebSocket** | Persistent bidirectional connection | Real-time UIs, dashboards, live tools |
|
||||
|
||||
Both systems emit the same kinds of events (e.g. `game.added`) but differ in how they deliver them.
|
||||
|
||||
---
|
||||
|
||||
## 2. When to Use Which
|
||||
|
||||
### Use Webhooks when:
|
||||
|
||||
- **Server-to-server** — Discord bots, Slack, logging pipelines, external APIs
|
||||
- **Stateless** — Your endpoint receives a POST, processes it, and returns. No long-lived connection
|
||||
- **Behind firewalls** — Your server can receive HTTP but may not hold open WebSocket connections
|
||||
- **Async delivery** — You’re fine with HTTP round-trip latency and want delivery logged and auditable
|
||||
|
||||
### Use WebSocket when:
|
||||
|
||||
- **Real-time UI** — Dashboards, admin panels, live session viewers
|
||||
- **Instant updates** — You need push-style notifications with minimal latency
|
||||
- **Persistent connection** — Your app keeps a live connection and subscribes to specific sessions
|
||||
- **Best-effort is fine** — WebSocket is push-only; there’s no built-in delivery log for events
|
||||
|
||||
---
|
||||
|
||||
## 3. Webhook Setup
|
||||
|
||||
Webhooks are registered via the REST API. See [Webhooks endpoints](../endpoints/webhooks.md) for full CRUD details.
|
||||
|
||||
### Create a Webhook
|
||||
|
||||
`POST /api/webhooks` with:
|
||||
|
||||
- `name` — Display name (e.g. `"Discord Bot"`)
|
||||
- `url` — Callback URL (must be a valid HTTP/HTTPS URL)
|
||||
- `secret` — Shared secret for signing payloads (HMAC-SHA256)
|
||||
- `events` — Array of event types that trigger this webhook (e.g. `["game.added"]`)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/webhooks" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Discord Bot",
|
||||
"url": "https://my-server.com/webhooks/jackbox",
|
||||
"secret": "mysecret123",
|
||||
"events": ["game.added"]
|
||||
}'
|
||||
```
|
||||
|
||||
The `events` array defines which events fire this webhook. Currently, the codebase triggers webhooks for **`game.added`** when a game is added to a session. The `triggerWebhook` function in `backend/utils/webhooks.js` is invoked from `sessions.js` on that event.
|
||||
|
||||
### Update, Enable/Disable, Delete
|
||||
|
||||
- **Update:** `PATCH /api/webhooks/{id}` — Change `name`, `url`, `secret`, `events`, or `enabled`
|
||||
- **Disable:** `PATCH /api/webhooks/{id}` with `"enabled": false` — Stops delivery without deleting config
|
||||
- **Delete:** `DELETE /api/webhooks/{id}` — Removes webhook and its logs
|
||||
|
||||
---
|
||||
|
||||
## 4. Webhook Delivery
|
||||
|
||||
### How it works
|
||||
|
||||
When an event occurs (e.g. a game is added), the server:
|
||||
|
||||
1. Finds all enabled webhooks subscribed to that event
|
||||
2. Sends an async HTTP POST to each webhook URL
|
||||
3. Logs each delivery attempt in `webhook_logs` (status, error, payload)
|
||||
|
||||
### Payload format
|
||||
|
||||
Each POST body is JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "game.added",
|
||||
"timestamp": "2026-03-15T20:30:00.000Z",
|
||||
"data": {
|
||||
"session": { "id": 3, "is_active": true, "games_played": 2 },
|
||||
"game": {
|
||||
"id": 42,
|
||||
"title": "Quiplash 3",
|
||||
"pack_name": "Jackbox Party Pack 7",
|
||||
"min_players": 3,
|
||||
"max_players": 8,
|
||||
"manually_added": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Headers include:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `X-Webhook-Event: game.added`
|
||||
- `X-Webhook-Signature: sha256=<hmac>` — Use your `secret` to verify the payload
|
||||
|
||||
### View delivery logs
|
||||
|
||||
`GET /api/webhooks/{id}/logs` returns recent delivery attempts (status, error message, payload).
|
||||
|
||||
### Test a webhook
|
||||
|
||||
`POST /api/webhooks/test/{id}` sends a dummy `game.added` event to the webhook URL. Delivery runs asynchronously; check logs for status.
|
||||
|
||||
---
|
||||
|
||||
## 5. WebSocket Events
|
||||
|
||||
The WebSocket server runs at `/api/sessions/live` on the same host and port as the HTTP API. See [WebSocket protocol](../websocket.md) for connection, authentication, and subscription details.
|
||||
|
||||
### Event types and audience
|
||||
|
||||
| Event | Broadcast to | Triggered by |
|
||||
|-------|--------------|--------------|
|
||||
| `session.started` | All authenticated clients | `POST /api/sessions` |
|
||||
| `game.added` | Session subscribers | `POST /api/sessions/{id}/games` |
|
||||
| `session.ended` | Session subscribers | `POST /api/sessions/{id}/close` |
|
||||
| `player-count.updated` | Session subscribers | `PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count` |
|
||||
|
||||
`session.started` goes to every authenticated client. The others go only to clients that have subscribed to the relevant session via `{ "type": "subscribe", "sessionId": 3 }`.
|
||||
|
||||
### Envelope format
|
||||
|
||||
All events use this envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "<event-type>",
|
||||
"timestamp": "2026-03-15T20:30:00.000Z",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
`data` contains event-specific fields (session, game, player count, etc.) as described in [WebSocket protocol](../websocket.md).
|
||||
|
||||
---
|
||||
|
||||
## 6. Comparison
|
||||
|
||||
| Feature | Webhooks | WebSocket |
|
||||
|---------|----------|-----------|
|
||||
| **Connection** | Stateless HTTP | Persistent |
|
||||
| **Auth** | Secret in config | JWT per connection |
|
||||
| **Events** | `game.added` | `session.started`, `game.added`, `session.ended`, `player-count.updated` |
|
||||
| **Latency** | Higher (HTTP round trip) | Lower (push) |
|
||||
| **Reliability** | Logged, auditable | Best-effort |
|
||||
|
||||
---
|
||||
|
||||
## 7. Example: Discord Bot
|
||||
|
||||
Use a webhook to post game additions to a Discord channel. You’ll need:
|
||||
|
||||
1. A webhook created in the Game Picker API pointing to your server
|
||||
2. A small server that receives the webhook and forwards to Discord’s Incoming Webhook
|
||||
|
||||
**Webhook receiver (Node.js):**
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
app.post('/webhooks/jackbox', express.json(), (req, res) => {
|
||||
const signature = req.headers['x-webhook-signature'];
|
||||
const payload = JSON.stringify(req.body);
|
||||
|
||||
// Verify HMAC-SHA256 using your webhook secret
|
||||
const expected = 'sha256=' + crypto
|
||||
.createHmac('sha256', process.env.WEBHOOK_SECRET)
|
||||
.update(payload)
|
||||
.digest('hex');
|
||||
|
||||
if (signature !== expected) {
|
||||
return res.status(401).send('Invalid signature');
|
||||
}
|
||||
|
||||
if (req.body.event === 'game.added') {
|
||||
const { session, game } = req.body.data;
|
||||
const discordPayload = {
|
||||
content: `🎮 **${game.title}** added to session #${session.id} (${game.min_players}-${game.max_players} players)`
|
||||
};
|
||||
|
||||
fetch(process.env.DISCORD_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(discordPayload)
|
||||
}).catch(err => console.error('Discord post failed:', err));
|
||||
}
|
||||
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
```
|
||||
|
||||
Register the Game Picker webhook with your server’s URL (e.g. `https://my-bot.example.com/webhooks/jackbox`), set `events` to `["game.added"]`, and use the same `secret` in your server’s `WEBHOOK_SECRET`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-references
|
||||
|
||||
- **[Webhooks endpoints](../endpoints/webhooks.md)** — Full CRUD, request/response schemas, errors
|
||||
- **[WebSocket protocol](../websocket.md)** — Connection, auth, subscriptions, event payloads
|
||||
Reference in New Issue
Block a user