API Reference
POST /v1/poll
Exchange a continuation token for the next batch of messages and the token that follows it.
Advances the stream by one step. Call it in a loop until token comes back
null.
POST /v1/poll
Content-Type: application/jsonRequest
| Field | Type | Required | Description |
|---|---|---|---|
token |
string |
yes | The continuation token from the previous response |
client |
string |
yes | The client label from the session response |
endpoint |
"get_live_chat" | "get_live_chat_replay" |
yes | The endpoint from the session response |
apiKey |
string |
yes | The API key from the session response |
webVersion |
string |
conditional | Required when client is "WEB" |
{
"token": "0ofMyAN...",
"client": "WEB",
"endpoint": "get_live_chat",
"apiKey": "AIzaSy...",
"webVersion": "2.20240726.00.00"
}webVersion is what makes the WEB client's version string valid; omitting it
for WEB is rejected up front with 400 INVALID_REQUEST rather than being sent
to YouTube. Other clients carry a pinned version and ignore the field.
Response
| Field | Type | Description |
|---|---|---|
token |
string | null |
The next continuation token, or null when the chat has ended |
timeoutMs |
number |
How long YouTube suggests waiting before the next poll |
messages |
Message[] |
Messages in this batch; frequently empty on a quiet stream |
{
"token": "0ofMyANyaHR0cHM6...",
"timeoutMs": 5000,
"messages": [
{
"type": "chat",
"id": "ChwKGkNJ...",
"timestamp": "1:23 PM",
"author": "Some Viewer",
"message": "first",
"amount": "",
"color": "",
"parts": [{ "kind": "text", "text": "first" }]
}
]
}An empty messages array is normal and is not an error — it means nothing was
said in that window. Keep polling.
Token discipline
Each response's token replaces the one you sent. There is exactly one
live token per stream at a time.
let token = session.token
while (true) {
const poll = await postPoll({ ...session, token })
handle(poll.messages)
if (poll.token === null) break // chat is over
token = poll.token // advance
await sleep(poll.timeoutMs)
}Reusing a spent token, or resuming with one saved from an earlier run, is the
usual cause of 410 TOKEN_EXPIRED. Recover by calling
/v1/sessions again for a fresh token — the client
library does this automatically.
Pacing
timeoutMs is YouTube's own suggestion and varies with chat volume. When
YouTube sends no value, the server substitutes a 1000 ms default.
The endpoint itself imposes no rate limit, but polling far faster than
timeoutMs gains you nothing and risks upstream throttling. The client library
clamps the delay between 500 ms and 15 000 ms.
Errors
| Status | Code | Cause |
|---|---|---|
400 |
INVALID_REQUEST |
Body failed validation, or client is WEB with no webVersion |
410 |
TOKEN_EXPIRED |
The continuation token is no longer accepted |
422 |
UNKNOWN_CLIENT |
client is not one of the known innertube client labels |
502 |
UPSTREAM_ERROR |
YouTube returned an error for this poll |
Full descriptions in Errors.