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) |
| 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.
**Real-time tracking:** Live votes also broadcast a `vote.received` WebSocket event to all clients subscribed to the active session. This enables stream overlays and bots to react to votes in real-time without polling. See [WebSocket vote.received](../websocket.md#votereceived).
---
## 3b. Querying Vote Data
Two endpoints expose vote data for reading:
- **`GET /api/sessions/{id}/votes`** — Per-game vote breakdown for a session. Returns aggregated `upvotes`, `downvotes`, `net_score`, and `total_votes` per game. See [Sessions votes endpoint](../endpoints/sessions.md#get-apisessionsidvotes).
- **`GET /api/votes`** — Paginated global vote history with filtering by `session_id`, `game_id`, `username`, and `vote_type`. Returns individual vote records. See [Votes list endpoint](../endpoints/votes.md#get-apivotes).