# Votes Endpoints Real-time popularity voting. Bots or integrations send votes during live gaming sessions. Two voting mechanisms are supported: - **`thisgame++`/`thisgame--`** — votes for the game currently being played, matched via timestamp intervals. - **Ticker voting** — votes for a specific game by its ticker symbol (e.g. `QPL3` for Quiplash 3), regardless of what is currently being played. ## Endpoint Summary | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/votes` | None | Paginated vote history with filtering | | POST | `/api/votes/live` | Bearer | Submit a live vote (up/down) | --- ## GET /api/votes Paginated vote history with filtering. Use query parameters to filter by session, game, username, or vote type. ### Authentication None. ### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | session_id | int | No | — | Filter by session ID | | game_id | int | No | — | Filter by game ID | | username | string | No | — | Filter by voter username | | vote_type | string | No | — | `"up"` or `"down"` | | page | int | No | 1 | Page number | | limit | int | No | 50 | Items per page (max 100) | ### Response **200 OK** Results are ordered by `timestamp DESC`. The `vote_type` field is returned as `"up"` or `"down"` (not raw integers). ```json { "votes": [ { "id": 891, "session_id": 5, "game_id": 42, "game_title": "Quiplash 3", "pack_name": "Party Pack 7", "username": "viewer123", "vote_type": "up", "timestamp": "2026-03-15T20:29:55.000Z", "created_at": "2026-03-15T20:29:56.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 237, "total_pages": 5 } } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "..." }` | Invalid `session_id`, `game_id`, or `vote_type` | | 200 | `{ "votes": [], "pagination": { "page": 1, "limit": 50, "total": 0, "total_pages": 0 } }` | No results match the filters | --- ## POST /api/votes/live Submit a real-time up/down vote. Supports two independent voting mechanisms: - **Ticker voting** — include a `ticker` field to vote for a specific game by symbol. The game is resolved globally and does not need to be in the active session. - **`thisgame++`/`thisgame--` voting** — omit `ticker` to vote for the game currently being played, matched via timestamp intervals against `session_games`. ### Authentication Bearer token required. Include in header: `Authorization: Bearer `. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | username | string | Yes | Identifier for the voter (used for deduplication) | | vote | string | Yes | `"up"` or `"down"` | | timestamp | string | Yes | ISO 8601 timestamp when the vote occurred | | ticker | string | No | Ticker symbol identifying the game (e.g. `QPL3`, `TMP2`). When provided, the game is resolved by ticker and timestamp matching is skipped. | **Ticker vote:** ```json { "username": "viewer123", "vote": "up", "timestamp": "2026-03-15T20:30:00Z", "ticker": "QPL3" } ``` **`thisgame++`/`thisgame--` vote (no ticker):** ```json { "username": "viewer123", "vote": "up", "timestamp": "2026-03-15T20:30:00Z" } ``` ### Behavior - Finds the active session (single session with `is_active = 1`). - **With `ticker`:** Looks up the game globally by ticker symbol. The game does not need to be part of the active session. - **Without `ticker`:** Matches the vote timestamp to the game being played at that time (uses interval between consecutive `played_at` timestamps). - Updates game `upvotes`, `downvotes`, and `popularity_score` atomically in a transaction. - **Deduplication:** Rejects votes from the same username within a 1-second window (409 Conflict). - Broadcasts a `vote.received` WebSocket event to all clients subscribed to the active session. See [WebSocket Protocol](../websocket.md#votereceived) for event payload. ### Response **200 OK** The `ticker` field is included in the response when the vote was submitted with a ticker. ```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", "ticker": "QPL3" } } ``` ### Error Responses | Status | Body | When | |--------|------|------| | 400 | `{ "error": "Missing required fields: username, vote, timestamp" }` | Missing required fields | | 400 | `{ "error": "vote must be either \"up\" or \"down\"" }` | Invalid vote value | | 400 | `{ "error": "Invalid timestamp format. Use ISO 8601 format (e.g., 2025-11-01T20:30:00Z)" }` | Invalid timestamp | | 404 | `{ "error": "No active session found" }` | No session with `is_active = 1` | | 404 | `{ "error": "Unknown ticker 'XYZ'" }` | Ticker does not match any game | | 404 | `{ "error": "No games have been played in the active session yet" }` | Active session has no games (timestamp voting only) | | 404 | `{ "error": "Vote timestamp does not match any game in the active session", "debug": { ... } }` | Timestamp outside any game interval (timestamp voting only) | | 409 | `{ "error": "Duplicate vote detected (within 1 second of previous vote)", "message": "Please wait at least 1 second between votes", "timeSinceLastVote": 0.5 }` | Same username voted within 1 second | | 500 | `{ "error": "..." }` | Server error | ### Examples **Ticker vote:** ```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", "ticker": "QPL3" }' ``` **`thisgame++` vote (no ticker):** ```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" }' ```