Update REST endpoint docs (votes.md, sessions.md), WebSocket protocol (websocket.md), OpenAPI spec, and voting guide with the new GET /api/votes, GET /api/sessions/:id/votes, and vote.received event. Made-with: Cursor
21 KiB
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
[
{
"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
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)
{
"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)
{
"session": null,
"message": "No active session"
}
Example
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
{
"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
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") |
{
"notes": "Friday game night"
}
Response
201 Created
{
"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
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) |
{
"notes": "Great session!"
}
Response
200 OK
{
"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
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
{
"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
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
[
{
"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
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
{
"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
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 |
{
"game_id": 42,
"manually_added": true,
"room_code": "ABCD"
}
Response
201 Created
{
"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
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 |
{
"chatData": [
{
"username": "viewer1",
"message": "thisgame++",
"timestamp": "2026-03-15T20:30:00Z"
},
{
"username": "viewer2",
"message": "thisgame--",
"timestamp": "2026-03-15T20:31:00Z"
}
]
}
Response
200 OK
{
"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
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" |
{
"status": "played"
}
Response
200 OK
{
"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
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
{
"message": "Game removed from session successfully"
}
Error Responses
| Status | Body | When |
|---|---|---|
| 404 | { "error": "Session game not found" } |
Invalid sessionId or sessionGameId |
Example
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) |
{
"room_code": "XY9Z"
}
Response
200 OK
Returns the updated SessionGame object with joined game data.
{
"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
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, filenamesession-{id}.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, filenamesession-{id}.txt— human-readable sections with headers.
Error Responses
| Status | Body | When |
|---|---|---|
| 404 | { "error": "Session not found" } |
Invalid session ID |
Example
# 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
{
"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
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
{
"message": "Room monitor and player count check stopped",
"status": "stopped"
}
Example
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 |
{
"player_count": 6
}
Response
200 OK
{
"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
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}'