Error codes
The bot API reports failures with an HTTP status code and a detailCode. The
same status code can call for different handling, so branch on detailCode as well.
Some responses have no detailCode. The gateway in front checks the token, permissions, and call quota first,
so a 401, 403, or 429 blocked there has no detailCode. Branch on the status code first,
and look at detailCode only when it is there.
Response format
The bot runtime API responds in RFC 7807 format.
{
"instance": "/v1/live",
"status": 404,
"title": "Not Found",
"detailCode": "OAPI_MNGR_0301",
"message": "The DJ is not broadcasting right now.",
"userId": null,
"extra": null,
"timestamp": 1788407119681
}
| Field | Description |
|---|---|
detailCode | The value that decides your handling. This is the contract. |
message | A human-readable explanation. The wording can change, so do not branch on it. It is also not guaranteed to match your service language, so do not show it to your users — supply your own copy instead. |
status | The HTTP status code. |
title | The standard reason phrase for the status (Not Found, Forbidden, …). Do not branch on it. |
instance | The path you called. |
userId · extra | Slots the internal framework fills. On the bot runtime API they are always null. |
timestamp | When the response was built, in epoch milliseconds. |
userId and extra are not for you to use. They are still present as null, so locking your
schema with additionalProperties: false breaks here. Let unknown fields through.
Errors on the event stream arrive as SSE frames. GET /v1/live/events responds as text/event-stream, so even a failed connection has a
body that starts with data:. Parsing that body directly as JSON throws.
Branch on the HTTP status code, and if you need the body, strip the data: prefix first.
HTTP/2 404
content-type: text/event-stream;charset=UTF-8
data:{"status":404,"detailCode":"OAPI_MNGR_0301","message":"The DJ is not broadcasting right now."}
Codes a bot will meet
These come from every endpoint except the two under /v1/oauth. The
endpoint list shows what those are.
400 — fix the request
detailCode | What happened | What to do |
|---|---|---|
OAPI_MNGR_0103 | The request is malformed — a broken body, a missing required field, a missing Content-Type, or the wrong method. | Fix the request format. Sending the same request again gives the same result. |
OAPI_MNGR_0108 | The message is empty, including whitespace-only. | Check what you are sending. Resending the same request gives the same result. |
OAPI_MNGR_0109 | The message is longer than 200 characters. An emoji can count as two. | Shorten it. |
401 — a human has to authorize again
detailCode | What happened | What to do |
|---|---|---|
| none | The token is missing or invalid. Expiry, revocation, the DJ disconnecting the integration, and app suspension all land here. | Try refresh once; if it is still 401, stop and ask the DJ to authorize again. |
OAPI_MNGR_0203 | Same as above. Within a minute of a token being revoked, it can arrive with this code. | Same as above. |
Do not retry a 401 forever. Once the integration is disconnected, retrying fails every time while burning through
your call quota.
The reasons are not broken out because doing so would leak whether a token exists. The bot's response is re-authorization in every case.
403 — blocked by a permission or a state
detailCode | What happened | What to do |
|---|---|---|
| none | The permission this endpoint needs is missing. | Re-consent is enough. The integration itself is alive. |
OAPI_MNGR_0208 | You cannot post to this broadcast right now — chat is frozen, or the bot is chat-banned. | The two cases are not distinguished. Retry shortly, and tell the DJ if it keeps failing. |
OAPI_MNGR_0209 | The bot cannot join the broadcast. The DJ may have blocked it. | Neither re-consent nor re-authorization helps. The DJ has to lift it. |
Tell a 403 apart by whether it has a detailCode. Without one, a permission is missing; with one, it is a broadcast-side state (0208, 0209).
Do not lump 401 and 403 together. 401 means the DJ has to authorize from scratch, while a 403 for a missing permission just
needs an extra permission. Handling them the same way tears down a perfectly good
integration.
404 — not on air
detailCode | What happened | What to do |
|---|---|---|
OAPI_MNGR_0301 | The DJ is not broadcasting right now. | This is a state, not an error. On the event stream, back off and connect again; on a read, call again shortly. |
429 — too many calls
detailCode | What happened | What to do |
|---|---|---|
OAPI_MNGR_0302 | You are sending chat too frequently. | Wait a moment and send again. |
OAPI_MNGR_0302 arrives not only when your bot sends too much but also when chat in
that broadcast is busy overall. The limit is shared across the broadcast, so slowing
your own rate may not clear it right away.
A 429 with no detailCode is the API call quota. That response has an empty body.
Parsing the body throws, so branch on the status code alone. There are two quotas.
| Quota | When exceeded | When to call again |
|---|---|---|
| Per second | 429, no body, no Retry-After | Retry with exponential backoff. |
| Per day | 429, no body · with Retry-After | Wait Retry-After seconds. It lasts until the next reset (midnight). |
The daily quota is counted separately for the app total and for each permission, and you are blocked when either is exceeded. The
x-ratelimit-daily-scope header tells you which one you hit — if present, only that permission is blocked; if absent, the whole app is.
Slow down early using the response headers
The quota headers are attached to successful responses too. Watch these values and slow down before you get a 429.
| Header | Meaning |
|---|---|
x-ratelimit-remaining | The per-second tokens left right now. At 0, the next request gets a 429. |
x-ratelimit-replenish-rate | How many tokens are refilled per second. |
x-ratelimit-burst-capacity | The ceiling for a short burst. |
x-ratelimit-daily-limit | The daily call limit. |
x-ratelimit-daily-remaining | How many calls are left today. |
x-ratelimit-daily-reset | When the next reset happens, in epoch seconds. |
x-ratelimit-daily-scope | Sent only on a daily-quota 429. The permission that was hit. |
The event stream (GET /v1/live/events) has no quota headers — that path does not count against the call quota.
The per-second quota is separate for each DJ integration, while the daily quota is summed across the app. Adding integrations does not raise the daily total.
You can also see today's usage in the app details of the Developer Center console.
500 — server error
detailCode | What happened | What to do |
|---|---|---|
OAPI_MNGR_1002 | Server error. | Retry with exponential backoff. Fixing the request changes nothing. |
It can come from any endpoint. Unlike a 502 (OAPI_MNGR_2302, OAPI_MNGR_2303), it does
not tell you where it came from, so there is one thing to do: retry with exponential
backoff.
502 — try again shortly
detailCode | What happened | What to do |
|---|---|---|
OAPI_MNGR_2302 | Broadcast server error. | Retry with exponential backoff. |
OAPI_MNGR_2303 | Chat server error. | Retry with exponential backoff. |
Token endpoint errors use a different format
POST /v1/oauth/token and POST /v1/oauth/revoke follow the OAuth standard (RFC 6749)
as is. The field is error, not detailCode. The full spec is in Tokens.
{
"error": "invalid_grant",
"error_description": "code is expired"
}
error | HTTP | What happened | What to do |
|---|---|---|---|
invalid_request | 400 | A required parameter is missing. | Check the request format. |
invalid_client | 401 | The Client ID or Client Secret does not match. | Check your credentials. |
invalid_grant | 400 | The code or refresh_token expired or was already used. | A code must be used within 60 seconds. If the refresh_token expired, ask the DJ to authorize again. |
unsupported_grant_type | 400 | An unsupported grant_type. | Use authorization_code or refresh_token. |
Related documents
- Tokens — issuing, refreshing, revoking, and the OAuth error format.
- Endpoint list — the full endpoint list.
- Event stream — the stream's reconnection strategy.
- Send chat — handling send failures.
- Choosing permissions — the permission list to check when you get a
403.