# Sessions Endpoints Sessions represent a gaming night. Only one session can be active at a time. Games are added to the active session as they're played. Sessions track game status, room codes, player counts, and chat logs for voting. **IMPORTANT:** In session game sub-routes like `/api/sessions/{sessionId}/games/{sessionGameId}/status`, the `sessionGameId` parameter refers to the `session_games.id` row ID, NOT `games.id`. ## Endpoint Summary | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/sessions` | No | List all sessions with games_played count | | GET | `/api/sessions/active` | No | Get the active session (or null) | | GET | `/api/sessions/{id}` | No | Get a session by ID | | POST | `/api/sessions` | Bearer | Create a new session | | POST | `/api/sessions/{id}/close` | Bearer | Close a session | | DELETE | `/api/sessions/{id}` | Bearer | Delete a closed session | | GET | `/api/sessions/{id}/games` | No | List games in a session | | GET | `/api/sessions/{id}/votes` | No | Get per-game vote breakdown for a session | | POST | `/api/sessions/{id}/games` | Bearer | Add a game to a session | | POST | `/api/sessions/{id}/chat-import` | Bearer | Import chat log for vote processing | | GET | `/api/sessions/{id}/export` | Bearer | Export session (JSON or TXT) | | PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/status` | Bearer | Update session game status | | DELETE | `/api/sessions/{sessionId}/games/{sessionGameId}` | Bearer | Remove game from session | | PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/room-code` | Bearer | Update room code for session game | | POST | `/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check` | Bearer | Start room monitor | | POST | `/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check` | Bearer | Stop room monitor | | PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/player-count` | Bearer | Update player count for session game | --- ## GET /api/sessions List all sessions with a `games_played` count. Ordered by `created_at` DESC. ### Authentication None. ### Response **200 OK** ```json [ { "id": 5, "notes": "Friday game night", "is_active": 1, "created_at": "2026-03-15T19:00:00.000Z", "closed_at": null, "games_played": 3 }, { "id": 4, "notes": "Last week's session", "is_active": 0, "created_at": "2026-03-08T18:30:00.000Z", "closed_at": "2026-03-08T23:15:00.000Z", "games_played": 5 } ] ``` ### Example ```bash curl "http://localhost:5000/api/sessions" ``` --- ## GET /api/sessions/active Get the active session. Returns the session object directly if one is active, or a wrapper with `session: null` if none. ### Authentication None. ### Response **200 OK** (active session exists) ```json { "id": 5, "notes": "Friday game night", "is_active": 1, "created_at": "2026-03-15T19:00:00.000Z", "closed_at": null, "games_played": 3 } ``` **200 OK** (no active session) ```json { "session": null, "message": "No active session" } ``` ### Example ```bash curl "http://localhost:5000/api/sessions/active" ``` --- ## GET /api/sessions/{id} Get a single session by ID. ### Authentication None. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Response **200 OK** ```json { "id": 5, "notes": "Friday game night", "is_active": 1, "created_at": "2026-03-15T19:00:00.000Z", "closed_at": null, "games_played": 3 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```bash curl "http://localhost:5000/api/sessions/5" ``` --- ## POST /api/sessions Create a new session. Only one active session is allowed at a time. Triggers WebSocket `session.started` broadcast to all authenticated clients. ### Authentication Bearer token required. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | notes | string | No | Optional notes (e.g., "Friday game night") | ```json { "notes": "Friday game night" } ``` ### Response **201 Created** ```json { "id": 5, "notes": "Friday game night", "is_active": 1, "created_at": "2026-03-15T19:00:00.000Z", "closed_at": null } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "An active session already exists. Please close it before creating a new one.", "activeSessionId": 5 }` | An active session already exists | ### Example ```bash curl -X POST "http://localhost:5000/api/sessions" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"notes": "Friday game night"}' ``` --- ## POST /api/sessions/{id}/close Close a session. Auto-sets all games with status `playing` to `played`. Optional body updates session notes. Triggers WebSocket `session.ended` broadcast to session subscribers. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | notes | string | No | Optional notes (updates session notes) | ```json { "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 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "Session is already closed" }` | Session was already closed | | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```bash curl -X POST "http://localhost:5000/api/sessions/5/close" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"notes": "Great session!"}' ``` --- ## DELETE /api/sessions/{id} Delete a session. Cannot delete active sessions — close first. Cascades: deletes `chat_logs` and `session_games`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Response **200 OK** ```json { "message": "Session deleted successfully", "sessionId": 5 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "Cannot delete an active session. Please close it first." }` | Session is active | | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```bash curl -X DELETE "http://localhost:5000/api/sessions/5" \ -H "Authorization: Bearer $TOKEN" ``` --- ## GET /api/sessions/{id}/games List all games in a session. Returns SessionGame objects joined with game data. Ordered by `played_at` ASC. ### Authentication None. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Response **200 OK** ```json [ { "id": 12, "session_id": 5, "game_id": 42, "manually_added": 1, "status": "played", "room_code": "ABCD", "played_at": "2026-03-15T19:15:00.000Z", "player_count": 6, "pack_name": "Jackbox Party Pack 7", "title": "Quiplash 3", "game_type": "Writing", "min_players": 3, "max_players": 8, "popularity_score": 12, "upvotes": 15, "downvotes": 3 }, { "id": 13, "session_id": 5, "game_id": 38, "manually_added": 0, "status": "playing", "room_code": "XY9Z", "played_at": "2026-03-15T20:00:00.000Z", "player_count": null, "pack_name": "Jackbox Party Pack 6", "title": "Trivia Murder Party 2", "game_type": "Trivia", "min_players": 1, "max_players": 8, "popularity_score": 8, "upvotes": 10, "downvotes": 2 } ] ``` ### Example ```bash curl "http://localhost:5000/api/sessions/5/games" ``` --- ## GET /api/sessions/{id}/votes Get per-game vote breakdown for a session. Aggregates votes from the `live_votes` table by game. Results ordered by `net_score` DESC. ### Authentication None. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Response **200 OK** ```json { "session_id": 5, "votes": [ { "game_id": 42, "title": "Quiplash 3", "pack_name": "Party Pack 7", "upvotes": 14, "downvotes": 3, "net_score": 11, "total_votes": 17 } ] } ``` Returns 200 with an empty `votes` array when the session has no votes. ### Error Responses | Status | Body | When | |--------|------|------| | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```bash curl "http://localhost:5000/api/sessions/5/votes" ``` --- ## POST /api/sessions/{id}/games Add a game to a session. Side effects: increments game `play_count`, sets previous `playing` games to `played` (skipped games stay skipped), triggers `game.added` webhook and WebSocket event, and auto-starts room monitor if `room_code` is provided. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | game_id | integer | Yes | Game ID (from games table) | | manually_added | boolean | No | Whether the game was manually added (default: false) | | room_code | string | No | 4-character room code; if provided, auto-starts room monitor | ```json { "game_id": 42, "manually_added": true, "room_code": "ABCD" } ``` ### Response **201 Created** ```json { "id": 14, "session_id": 5, "game_id": 42, "manually_added": 1, "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 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "game_id is required" }` | Missing game_id | | 400 | `{ "error": "Cannot add games to a closed session" }` | Session is closed | | 404 | `{ "error": "Session not found" }` | Invalid session ID | | 404 | `{ "error": "Game not found" }` | Invalid game_id | ### Example ```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"}' ``` --- ## POST /api/sessions/{id}/chat-import Import chat log and process votes. Matches votes to games by timestamp intervals. `"thisgame++"` = upvote, `"thisgame--"` = downvote. Deduplicates by SHA-256 hash of `username:message:timestamp`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | chatData | array | Yes | Array of `{ username, message, timestamp }` objects | ```json { "chatData": [ { "username": "viewer1", "message": "thisgame++", "timestamp": "2026-03-15T20:30:00Z" }, { "username": "viewer2", "message": "thisgame--", "timestamp": "2026-03-15T20:31:00Z" } ] } ``` ### Response **200 OK** ```json { "message": "Chat log imported and processed successfully", "messagesImported": 150, "duplicatesSkipped": 3, "votesProcessed": 25, "votesByGame": { "42": { "title": "Quiplash 3", "upvotes": 15, "downvotes": 2 } }, "debug": { "sessionGamesTimeline": [ { "title": "Quiplash 3", "played_at": "2026-03-15T20:00:00.000Z", "played_at_ms": 1742068800000 } ], "voteMatches": [] } } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "chatData must be an array" }` | chatData missing or not an array | | 400 | `{ "error": "No games played in this session to match votes against" }` | Session has no games | | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```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"}]}' ``` --- ## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/status Update the status of a session game. Valid values: `playing`, `played`, `skipped`. If setting to `playing`, auto-sets other `playing` games to `played`. **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | status | string | Yes | `"playing"`, `"played"`, or `"skipped"` | ```json { "status": "played" } ``` ### Response **200 OK** ```json { "message": "Status updated successfully", "status": "played" } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "Invalid status. Must be playing, played, or skipped" }` | Invalid status value | | 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId | ### Example ```bash curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"status": "played"}' ``` --- ## DELETE /api/sessions/{sessionId}/games/{sessionGameId} Remove a game from a session. Stops room monitor and player count check. **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Response **200 OK** ```json { "message": "Game removed from session successfully" } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId | ### Example ```bash curl -X DELETE "http://localhost:5000/api/sessions/5/games/14" \ -H "Authorization: Bearer $TOKEN" ``` --- ## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/room-code Update the room code for a session game. Room code must be exactly 4 characters, uppercase A–Z and 0–9 only (regex: `^[A-Z0-9]{4}$`). **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | room_code | string | Yes | 4 uppercase alphanumeric chars (A-Z, 0-9) | ```json { "room_code": "XY9Z" } ``` ### Response **200 OK** Returns the updated SessionGame object with joined game data. ```json { "id": 14, "session_id": 5, "game_id": 42, "manually_added": 1, "status": "playing", "room_code": "XY9Z", "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, "popularity_score": 12, "upvotes": 15, "downvotes": 3 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "room_code is required" }` | Missing room_code | | 400 | `{ "error": "room_code must be exactly 4 alphanumeric characters (A-Z, 0-9)" }` | Invalid format | | 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId | ### Example ```bash 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"}' ``` --- ## GET /api/sessions/{id}/export Export session data as a file download. JSON format includes structured session, games, and chat_logs. TXT format is human-readable plaintext. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | id | integer | Session ID | ### Query Parameters | Name | Type | Required | Default | Description | |------|------|----------|---------|-------------| | format | string | No | `txt` | `"json"` or `"txt"` | ### Response **200 OK** - **JSON format**: Content-Type `application/json`, filename `session-{id}.json` ```json { "session": { "id": 5, "created_at": "2026-03-15T19:00:00.000Z", "closed_at": "2026-03-15T23:30:00.000Z", "is_active": false, "notes": "Friday game night", "games_played": 4 }, "games": [ { "title": "Quiplash 3", "pack": "Jackbox Party Pack 7", "players": "3-8", "type": "Writing", "played_at": "2026-03-15T19:15:00.000Z", "manually_added": true, "status": "played" } ], "chat_logs": [ { "username": "viewer1", "message": "thisgame++", "timestamp": "2026-03-15T19:20:00.000Z", "vote": "thisgame++" } ] } ``` - **TXT format**: Content-Type `text/plain`, filename `session-{id}.txt` — human-readable sections with headers. ### Error Responses | Status | Body | When | |--------|------|------| | 404 | `{ "error": "Session not found" }` | Invalid session ID | ### Example ```bash # JSON export curl -o session-5.json "http://localhost:5000/api/sessions/5/export?format=json" \ -H "Authorization: Bearer $TOKEN" # TXT export (default) curl -o session-5.txt "http://localhost:5000/api/sessions/5/export" \ -H "Authorization: Bearer $TOKEN" ``` --- ## POST /api/sessions/{sessionId}/games/{sessionGameId}/start-player-check Start the room monitor for a session game. The game must have a room code. **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Response **200 OK** ```json { "message": "Room monitor started", "status": "monitoring" } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "Game does not have a room code" }` | Session game has no room_code | | 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId | ### Example ```bash curl -X POST "http://localhost:5000/api/sessions/5/games/14/start-player-check" \ -H "Authorization: Bearer $TOKEN" ``` --- ## POST /api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check Stop the room monitor and player count check for a session game. **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Response **200 OK** ```json { "message": "Room monitor and player count check stopped", "status": "stopped" } ``` ### Example ```bash curl -X POST "http://localhost:5000/api/sessions/5/games/14/stop-player-check" \ -H "Authorization: Bearer $TOKEN" ``` --- ## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count Manually update the player count for a session game. Sets `player_count_check_status` to `completed`. Broadcasts WebSocket `player-count.updated`. **Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`. ### Authentication Bearer token required. ### Path Parameters | Name | Type | Description | |------|------|-------------| | sessionId | integer | Session ID | | sessionGameId | integer | Session game ID (`session_games.id`) | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | player_count | integer | Yes | Non-negative player count | ```json { "player_count": 6 } ``` ### Response **200 OK** ```json { "message": "Player count updated successfully", "player_count": 6 } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "player_count is required" }` | Missing player_count | | 400 | `{ "error": "player_count must be a positive number" }` | Invalid (NaN or negative) | | 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId | ### Example ```bash 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}' ```