Files
jackboxpartypack-gamepicker/docs/api/endpoints/votes.md
cottongin ea6e8db90b feat: ticker symbol voting for live votes
- Add ticker column to games table with migration
- Bootstrap tickers from tickers.json config on startup
- POST /api/votes/live accepts optional ticker field for direct game
  lookup (bypasses timestamp-interval matching)
- Ticker votes work for any game, not just session games
- Update API docs and add e2e tests for ticker voting
- Version bump to 0.6.5

Made-with: Cursor
2026-04-05 04:47:06 -04:00

6.1 KiB

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).

{
  "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 <token>.

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:

{
  "username": "viewer123",
  "vote": "up",
  "timestamp": "2026-03-15T20:30:00Z",
  "ticker": "QPL3"
}

thisgame++/thisgame-- vote (no ticker):

{
  "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 for event payload.

Response

200 OK

The ticker field is included in the response when the vote was submitted with a ticker.

{
  "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:

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):

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"
  }'