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 endedEach 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
- Quickstart — get a stream flowing in a couple of minutes.
- API Overview — the endpoint contract in detail.
- Client Usage — let the client library run the loop.