API Reference
Errors
The two places an error can appear, every error code with its HTTP status, and which ones are worth retrying.
Two shapes, one vocabulary
An error reaches you in one of two forms depending on when it happened.
Before the stream opens
An ordinary HTTP error with a real status code and a JSON body:
{
"error": {
"code": "NO_LIVE_CHAT",
"message": "no live chat found - video may not be live, have no chat, or be unavailable"
}
}After the stream opens
The status line has already been sent, so the failure arrives as a terminal SSE frame instead:
event: error
data: {"code":"UPSTREAM_ERROR","message":"..."}Same code vocabulary, no status code. The frame is terminal: the body closes
after it.
code is a stable enum and is what you should branch on. message is
human-readable, may be passed through from YouTube, and may change.
Every code
| Status | Code | Meaning |
|---|---|---|
400 |
INVALID_REQUEST |
A query parameter or request body failed schema validation |
400 |
INVALID_VIDEO_ID |
videoId was neither a recognized YouTube URL nor a bare 11-character id |
404 |
NO_LIVE_CHAT |
No liveChatRenderer or liveChatReplayRenderer with a continuation token could be found for the video |
410 |
TOKEN_EXPIRED |
The continuation token was rejected. Rare as a client-visible error — the server repairs this itself |
422 |
UNKNOWN_CLIENT |
An innertube client label was not recognized |
429 |
RATE_LIMITED |
YouTube is throttling this server's IP. Carries a Retry-After header |
502 |
SW_JS_FAILED |
sw.js could not be fetched, or the API key / client version could not be parsed out of it |
502 |
NO_WORKING_CLIENT |
Neither WEB nor TVHTML5 produced a usable response on either endpoint |
502 |
UPSTREAM_ERROR |
YouTube returned an error for the request |
500 |
INTERNAL |
An unexpected server-side failure, including a storage failure |
Notes on individual codes
INVALID_VIDEO_ID
The id must be extractable. These forms all work:
zvwJ29RFVww
https://www.youtube.com/watch?v=zvwJ29RFVww
https://youtu.be/zvwJ29RFVww
https://www.youtube.com/shorts/zvwJ29RFVww
https://www.youtube.com/embed/zvwJ29RFVww
https://www.youtube.com/live/zvwJ29RFVwwA channel URL or a playlist URL does not contain a video id and will fail.
NO_LIVE_CHAT
This is a 404 because it describes the video, not a fault. It covers several
real situations that are indistinguishable from the outside:
- the video is not live and has no replay chat,
- chat was disabled for the broadcast,
- replay chat has not finished processing, or has been turned off,
- the video is private, deleted, or region-blocked.
Retrying will not help unless the underlying situation changes.
RATE_LIMITED
YouTube throttles by IP, and the server shares one. A 429 carries a
Retry-After header in seconds — honour it. If it arrives as an error
frame instead there is no header, so wait at least 30 seconds, which is what
the server itself falls back to.
Reconnecting immediately is actively harmful: your reconnect drives a fresh bootstrap against the endpoint that is already refusing traffic.
TOKEN_EXPIRED
Mid-stream token expiry is the server's problem now, not yours. The poller re-bootstraps the room in place and the stream continues without a break. You will only see this code if that repair itself fails.
SW_JS_FAILED
The server refuses to hardcode the innertube API key and fetches it live per
bootstrap. If YouTube changes the shape of sw.js, this is the code you get.
It is a server-side problem, not something a caller can fix.
UPSTREAM_ERROR
Worth knowing: YouTube signals innertube failures with HTTP 200 and an
error object in the body. The server treats that as a failure rather than an
empty success, which is why a genuine upstream problem surfaces here as a
502 instead of silently looking like a quiet chat.
INTERNAL
Also covers storage failures. Which storage backend the server is running against is not exposed on the wire, deliberately — the detail is in the server's logs.
Which errors are retryable
| Code | Retry? |
|---|---|
RATE_LIMITED |
Yes — after Retry-After, never sooner |
UPSTREAM_ERROR |
Yes, with backoff — often transient |
INTERNAL |
Yes, with backoff |
SW_JS_FAILED |
Yes, with backoff, but repeated failures need a fix |
TOKEN_EXPIRED |
Yes — reconnect; the server re-bootstraps |
NO_WORKING_CLIENT |
Rarely — usually means the video is not readable |
NO_LIVE_CHAT |
No |
INVALID_VIDEO_ID |
No |
INVALID_REQUEST |
No |
UNKNOWN_CLIENT |
No |
The client library implements exactly this policy, with two separate
budgets: ordinary failures back off exponentially from 500 ms, capped at
15 000 ms, giving up after 3; rate limits wait out Retry-After and get
their own allowance of 8, reset by any successful reconnect. A server that
simply hangs up is treated as a reconnect and costs neither budget. See
Events & Options.
Errors in the client library
The client raises every failure as an ApiHttpError:
import { ApiHttpError } from '@gettersethya/yt-livechat-client'
client.on('error', (error) => {
// error.code -> the enum above
// error.message -> human-readable detail
// error.status -> HTTP status, or 0 if there was no response
// error.retryAfterMs -> set on a rate limit; how long to wait
if (error.code === 'NO_LIVE_CHAT') stopTrying()
})status is 0 when the failure did not come from an HTTP response — a
network error, an error frame, or an invalid video URL rejected in the
constructor.
An error event is not fatal. The client emits it and then decides
whether to continue; only end means the stream is over.