Search documentation

Search pages and headings

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 dev

The 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 dev and typecheck scripts build the client package first, because the server imports its shared schemas from that package's dist/.

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-client
pnpm add @gettersethya/yt-livechat-client
yarn add @gettersethya/yt-livechat-client
bun add @gettersethya/yt-livechat-client
import { 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.