Search documentation

Search pages and headings

Getting Started

Introduction

A stateless HTTP JSON API that turns YouTube live chat — live and replay — into normalized JSON.

yt-livechat reads YouTube live chat through YouTube's own internal ("innertube") endpoints and re-publishes it as plain JSON over HTTP. It works for both an ongoing live stream and the replay of a finished one, and it needs no Google API key and no OAuth.

Why it exists

The official YouTube Data API charges quota for live chat polling and stops working entirely once a broadcast ends. The innertube endpoints that the YouTube web player itself uses have neither limitation, but talking to them requires reproducing a browser's bootstrap: scraping an API key, matching a client version, brace-matching a JSON blob out of an HTML page, and then carrying an opaque continuation token forward on every request.

This project does that once, on the server, and hands you a stable contract.

The shape of the system

There are two published pieces.

Package What it is
apps/api The HTTP server. Bun runtime, effect/unstable/http router. Exposes /v1.
@gettersethya/yt-livechat-client A TypeScript client that drives the polling loop for you.

The client never talks to YouTube — it only talks to your API server. The server never talks to your client's storage — it holds nothing between requests.

The server is stateless

This is the single most important property to understand, because it shapes the whole API.

One request equals one call to YouTube. There is no session store, no cache, no background polling loop, and no retry or sleep inside a handler. Everything needed to continue the stream — the continuation token, the chosen client, the endpoint, the API key — is returned to you and travels back in the body of your next request.

The practical consequences:

  • You can run as many server instances as you like behind a load balancer with no shared state and no sticky sessions.
  • Restarting the server does not interrupt any consumer that already holds a token.
  • You own the polling cadence. The server tells you how long to wait via timeoutMs, but nothing enforces it.

How a stream is read

POST /v1/sessions   ->  bootstrap: apiKey, webVersion, first token, timeoutMs
        |
        v
POST /v1/poll       ->  messages + the NEXT token
        |
        +-- token !== null -> wait timeoutMs, poll again with the new token
        |
        +-- token === null -> the chat has ended

Each poll response replaces your token. Reusing a spent token is not supported and is the usual cause of a TOKEN_EXPIRED failure.

What you get back

Every chat event is normalized into one flat Message shape regardless of which YouTube renderer produced it — a plain message, a Super Chat, a sticker, a membership, a gift, or a deletion all arrive with the same fields. Message bodies come in two parallel forms: message, a flattened plain-text string, and parts, a structured array that preserves custom emoji so you can render them as images.

See Message Schema for the field list and Message Types for the full set of type values.

Where to go next