Polls & predictions
With the polls:manage scope, your app can run polls and predictions on a channel, the same ones the creator starts from the Stream Manager. Viewers vote and predict on the watch page; your app starts, ends and settles them.
The shapes are Twitch's Helix ones (data: […], uppercase statuses), so a Twitch bot ports with a base-URL change. To hear about votes and predictions as they happen, also ask for events:read and listen for the EventSub types below.
One poll and one prediction can run on a channel at a time. Starting either needs the channel to be live. Predictions use channel points only, never money, so the channel needs channel points turned on.
Get the poll
GET /api/apps/channels/{channelId}/polls returns the running poll, or the latest one if none is running. data is empty when the channel has never run a poll.
{
"success": true,
"data": [
{
"id": "6c1f0e2a-8b47-4d3e-9a15-0f7e2c4b9d31",
"broadcaster_id": "1f9a3b6c-…",
"broadcaster_name": "Pixel Panda",
"broadcaster_login": "pixelpanda",
"title": "Which map next?",
"choices": [
{ "id": "c0d1…", "title": "Dust", "votes": 41, "channel_points_votes": 6, "bits_votes": 0 },
{ "id": "c0d2…", "title": "Mirage", "votes": 57, "channel_points_votes": 20, "bits_votes": 0 }
],
"bits_voting_enabled": false,
"bits_per_vote": 0,
"channel_points_voting_enabled": true,
"channel_points_per_vote": 100,
"status": "ACTIVE",
"duration": 120,
"started_at": "2026-10-11T20:40:00Z",
"ended_at": null
}
]
}
status is ACTIVE, COMPLETED (time ran out), TERMINATED (ended early) or ARCHIVED (ended and hidden from viewers). votes counts every vote; channel_points_votes is how many of them were bought with points. Xive has no Bits, so the Bits fields are always off.
Start a poll
POST /api/apps/channels/{channelId}/polls
| Field | Type | Notes |
|---|---|---|
title | string | Required. Up to 60 characters. |
choices | array | Required. 2–5 of {"title": …}, up to 25 characters each. |
duration | integer | Required. Seconds, 15–1800. |
channel_points_voting_enabled | boolean | Optional. Lets viewers buy extra votes with channel points. |
channel_points_per_vote | integer | Required with the above. 1–1,000,000. |
curl -X POST https://api.thexive.com/api/apps/channels/CHANNEL_ID/polls \
-H "Authorization: Bearer $XIVE_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{"title": "Which map next?", "choices": [{"title": "Dust"}, {"title": "Mirage"}], "duration": 120}'
Answers with the new poll in data. Every viewer gets one free vote; with channel-points voting on, they can add more votes for points. The title and choices go through Xive's content filter, which answers 400 on a refusal. 409 when a poll is already running or the channel isn't live.
End a poll
PATCH /api/apps/channels/{channelId}/polls with {"id": …, "status": "TERMINATED"} ends it now and shows the result. ARCHIVED ends it and hides it from viewers; it also works on a poll that has already ended. A poll that has already ended answers 409.
Get the prediction
GET /api/apps/channels/{channelId}/predictions returns the open prediction (taking predictions, or locked and waiting for a result), or the latest one.
{
"success": true,
"data": [
{
"id": "9a4e7d10-…",
"broadcaster_id": "1f9a3b6c-…",
"broadcaster_name": "Pixel Panda",
"broadcaster_login": "pixelpanda",
"title": "Do we beat the boss?",
"winning_outcome_id": null,
"outcomes": [
{
"id": "o1…", "title": "Yes", "users": 12, "channel_points": 8400, "color": "BLUE",
"top_predictors": [
{ "user_id": "…", "user_name": "Rook", "user_login": "rook", "channel_points_used": 2500, "channel_points_won": null }
]
},
{ "id": "o2…", "title": "No", "users": 9, "channel_points": 5100, "color": "PINK", "top_predictors": null }
],
"prediction_window": 120,
"status": "LOCKED",
"created_at": "2026-10-11T20:41:00Z",
"ended_at": null,
"locked_at": "2026-10-11T20:43:00Z"
}
]
}
status is ACTIVE, LOCKED, RESOLVED or CANCELED. Up to 10 top predictors per outcome. With two outcomes they are BLUE and PINK; with more, all BLUE.
Start a prediction
POST /api/apps/channels/{channelId}/predictions with title (up to 45 characters), outcomes (2–10 of {"title": …}, up to 25 characters each) and prediction_window (seconds, 30–1800).
curl -X POST https://api.thexive.com/api/apps/channels/CHANNEL_ID/predictions \
-H "Authorization: Bearer $XIVE_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{"title": "Do we beat the boss?", "outcomes": [{"title": "Yes"}, {"title": "No"}], "prediction_window": 120}'
Viewers put channel points on one outcome (and can add more to it), up to 250,000 points each. When the window closes, the prediction locks by itself.
Lock, resolve or cancel
PATCH /api/apps/channels/{channelId}/predictions with {"id": …, "status": …}:
| Status | Does | Works on |
|---|---|---|
LOCKED | Stop taking predictions now. | ACTIVE |
RESOLVED | Pick the winner (send winning_outcome_id). The winners share the whole pot. | ACTIVE, LOCKED |
CANCELED | Give everyone their points back. | ACTIVE, LOCKED |
curl -X PATCH https://api.thexive.com/api/apps/channels/CHANNEL_ID/predictions \
-H "Authorization: Bearer $XIVE_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{"id": "PREDICTION_ID", "status": "RESOLVED", "winning_outcome_id": "OUTCOME_ID"}'
A prediction is settled exactly once: a second resolve or cancel (from your app, the creator or another tool) answers 409 and pays nothing.
How the pot is paid
The pot is every point on every outcome. Each winner gets pot × their points ÷ the winning outcome's points, rounded down, so they always get at least their own points back. Rounding leaves a few points over (fewer than the number of winners); those go one each to the winners whose share was rounded down the most, ties going to the bigger prediction, then the earlier one. Every point in the pot is paid out. If nobody picked the winning outcome, everyone gets their points back.
EventSub
With events:read: channel.poll.begin, channel.poll.progress, channel.poll.end, channel.prediction.begin, channel.prediction.progress, channel.prediction.lock and channel.prediction.end, in Twitch's shapes. Progress events come at most about once a second.
{
"subscription_type": "channel.prediction.end",
"event": {
"id": "9a4e7d10-…",
"broadcaster_user_id": "1f9a3b6c-…", "broadcaster_user_login": "pixelpanda", "broadcaster_user_name": "Pixel Panda",
"title": "Do we beat the boss?",
"winning_outcome_id": "o1…",
"outcomes": [
{ "id": "o1…", "title": "Yes", "color": "blue", "users": 12, "channel_points": 8400,
"top_predictors": [ { "user_id": "…", "user_login": "rook", "user_name": "Rook", "channel_points_won": 4011, "channel_points_used": 2500 } ] }
],
"status": "resolved",
"started_at": "2026-10-11T20:41:00Z",
"ended_at": "2026-10-11T20:50:12Z"
}
}
channel_points_won is null in progress and lock events, and 0 for losers or a canceled prediction. A poll's channel.poll.end carries status (completed, terminated or archived) and ended_at.