Getting Started
Quickstart
Run the server and read a live chat, first with curl and then with the client library.
Prerequisites
- Bun — the API server's runtime.
- pnpm 10 — the workspace's package manager.
- A YouTube video that has live chat: either currently live, or a finished stream whose replay chat is still available.
Run the server
pnpm install
pnpm --filter @yt-livechat/api devThe server listens on port 3000 and mounts everything under /v1. CORS
headers are sent on every response, so a browser page may call it directly.
The api's
devandtypecheckscripts build the client package first, because the server imports its shared schemas from that package'sdist/.
Read a chat with curl
1. Open a session
videoId accepts a full YouTube URL or a bare 11-character id.
curl -s http://localhost:3000/v1/sessions \
-H 'Content-Type: application/json' \
-d '{"videoId":"https://www.youtube.com/watch?v=zvwJ29RFVww"}'The response carries everything the next request needs:
{
"videoId": "zvwJ29RFVww",
"chatType": "liveChatRenderer",
"client": "WEB",
"host": "https://www.youtube.com",
"endpoint": "get_live_chat",
"apiKey": "AIza...",
"webVersion": "2.2024...",
"token": "0ofMyAN...",
"timeoutMs": 1000,
"messages": []
}messages holds the backlog already present on the page. Send
"includeInitial": false to start from an empty backlog.
2. Poll for new messages
Copy token, client, endpoint, apiKey and webVersion straight across
from the session response:
curl -s http://localhost:3000/v1/poll \
-H 'Content-Type: application/json' \
-d '{
"token": "0ofMyAN...",
"client": "WEB",
"endpoint": "get_live_chat",
"apiKey": "AIza...",
"webVersion": "2.2024..."
}'{
"token": "0ofMyAN...",
"timeoutMs": 5000,
"messages": [
{
"type": "chat",
"id": "ChwKGkNJ...",
"timestamp": "1:23 PM",
"author": "Some Viewer",
"message": "hello :yt:",
"amount": "",
"color": "",
"parts": [
{ "kind": "text", "text": "hello " },
{
"kind": "emoji",
"shortcut": ":yt:",
"emoji_id": "yt",
"is_custom": false,
"mapped_unicode": "▶️",
"thumbnails": []
}
]
}
]
}3. Loop
Wait timeoutMs, then poll again with the token you just received. Each
response supersedes the previous token. When token comes back null, the
chat is over.
Read a chat with the client library
The loop above — token discipline, pacing, backoff, re-bootstrap on expiry — is what the client package does for you:
npm install @gettersethya/yt-livechat-clientpnpm add @gettersethya/yt-livechat-clientyarn add @gettersethya/yt-livechat-clientbun add @gettersethya/yt-livechat-clientimport { LiveChatApiClient } from '@gettersethya/yt-livechat-client'
const client = new LiveChatApiClient({
baseUrl: 'http://localhost:3000',
videoUrl: 'https://www.youtube.com/watch?v=zvwJ29RFVww',
})
client.on('connected', () => console.log('connected:', client.videoId))
client.on('message', (message) => console.log(`${message.author}: ${message.message}`))
client.on('error', (error) => console.error(error.code, error.message))
client.on('end', (reason) => console.log('ended:', reason))
await client.connect()
await client.start()start() resolves when the stream ends. Call client.stop() from anywhere to
break the loop; it only sets a flag and never blocks.
Check which clients work for a video
If a video returns no messages, ask the server to try every innertube client and endpoint combination and report what worked:
curl -s http://localhost:3000/v1/probe \
-H 'Content-Type: application/json' \
-d '{"videoId":"zvwJ29RFVww"}'See Probe for how to read the result.