This page lists every error code, what it means, and how to fix it. Come here when you have an error code in a log or on screen and need to know where it came from and what to change.
Errors happen at different steps of the flow — your backend minting a token, your user's app joining a call, a chat connection — and each step has its own list of codes. So the first question is: which step failed?
Which list is my error from?
| You saw the error… | It's from | Look in |
|---|---|---|
In your backend, when an @livqeno/server / livqeno call or a curl request failed | The REST API | HTTP error codes (RAVEN_*) |
In your browser or phone app, thrown by join() or in room.on('error') | The RTC SDK | RTC SDK errors |
In your app's chat code, or as a chat WebSocket close code (44xx) | Chat | Chat errors |
In a raw RTC signaling frame, or an RTC WebSocket close code (40xx) | Signaling | Signaling errors |
In the dashboard, livqeno errors list, or the Observability API, as a category like ICE_ERROR | Livqeno's error analysis | RTC error categories |
Some names, like TOKEN_EXPIRED, appear in more than one list. Use the
table above to know which one you're looking at.
Summary of the four lists:
| List | Where you see it | How many |
|---|---|---|
RAVEN_* | The code field of any HTTP error | 34 |
RTCErrorCode | Errors thrown or emitted by @livqeno/rtc | 13 |
ChatErrorCode | The chat WebSocket error frame and @livqeno/chat | 26 |
SignalingErrorCode | The RTC signaling WebSocket error frame | 20 |
There are also RTC error categories. These aren't response codes. They're labels Livqeno puts on failures after they happen, to help you debug.
What an HTTP error looks like
Any REST call can fail this way: from your backend with the API key, or from the dashboard and CLI. The body always has the same shape:
{
"code": "RAVEN_ROOM_NOT_FOUND",
"legacyCode": "NOT_FOUND",
"message": "Room not found",
"requestId": "req_d4a74eaeb970633fc42414a6",
"path": "/v1/rooms/2f1b6c3e-8d4a-4b7e-9c21-5a0e7d3f9b10"
}A validation error (RAVEN_VALIDATION_FAILED, 400) has two extra fields,
statusCode and error. Its message can be a list, one entry per bad
field:
{
"message": ["environment must be one of the following values: DEVELOPMENT, STAGING, PRODUCTION"],
"error": "Bad Request",
"statusCode": 400,
"code": "RAVEN_VALIDATION_FAILED",
"legacyCode": "VALIDATION_FAILED",
"requestId": "req_7f9f475ddcab0cb6f554f253",
"path": "/v1/projects/…/api-keys"
}So treat message as string | string[]. Ids in the URL are UUIDs. A badly
formed id gets a 400, not a 404.
| Field | What it is |
|---|---|
code | The error code. Check this one. |
legacyCode | Old, don't use. The code from before RAVEN_* existed. Will be removed. |
message | A readable message. Safe to log. Never contains secrets. |
requestId | Same as the x-request-id header. Include it in bug reports. |
path | The endpoint that failed. |
Some errors add more fields. For example, a 429 adds retryAfterSeconds.
Ignore fields you don't know.
Catching errors on your backend
The server SDKs throw one error type, LivqenoError, with the same code
and requestId as the HTTP body. A typical token endpoint on your
backend (TypeScript):
import { isLivqenoError } from '@livqeno/server';
try {
const grant = await livqeno.tokens.create({ room: roomId, identity: req.user.id });
res.json(grant);
} catch (error) {
if (isLivqenoError(error)) {
// Log requestId so you can match this to Livqeno's records later.
console.error(error.code, error.requestId);
if (error.code === 'RAVEN_ROOM_NOT_FOUND') return res.status(404).end();
}
throw error;
}HTTP error codes
These come back from the REST API. You'll mostly see them in your backend, when it creates rooms, tokens, or conversations with the API key. The dashboard and CLI show them too.
Login and access
Something is wrong with the credential itself: a bad API key, an expired token, or a dashboard sign-in that didn't finish.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_AUTH_ERROR | 401 | The credential is missing, broken, or rejected | Check the Authorization header, and that the key wasn't revoked |
RAVEN_TOKEN_EXPIRED | 401 | The token has expired | Make a new token. You don't need to log the user in again |
RAVEN_PERMISSION_DENIED | 403 | You're logged in, but not allowed to do this | Check the token's permissions, or the user's project role |
RAVEN_OAUTH_ERROR | 401 | OAuth sign-in didn't finish: expired state, failed code exchange, or the user cancelled | Start the sign-in again. Always safe |
RAVEN_OAUTH_EMAIL_UNAVAILABLE | 400 | The sign-in provider didn't give an email. Livqeno accounts need one | Fix it at the provider (for example, GitHub with no verified main email) |
RAVEN_OAUTH_EMAIL_UNVERIFIED | 403 | An account exists for this email, but the provider hasn't verified it | Verify the email at the provider. (Linking it anyway would allow account takeover) |
Not found
The thing doesn't exist for this credential. It may exist in another project or environment.
| Code | HTTP | What wasn't found |
|---|---|---|
RAVEN_NOT_FOUND | 404 | Something (no more specific code fits) |
RAVEN_PROJECT_NOT_FOUND | 404 | The project, or it isn't yours |
RAVEN_ROOM_NOT_FOUND | 404 | An RTC room |
RAVEN_PARTICIPANT_NOT_CONNECTED | 404 | A live session for that identity in the room (the moderation and metadata routes under /v1/rooms/:roomId/participants/:identity), or anyone at all for POST /v1/rooms/:roomId/data. They left, or never joined |
RAVEN_TRACK_NOT_FOUND | 404 | A track that participant is publishing (server mute with a trackId) |
RAVEN_CONVERSATION_NOT_FOUND | 404 | A chat conversation |
RAVEN_MESSAGE_NOT_FOUND | 404 | A message in this project |
RAVEN_ATTACHMENT_NOT_FOUND | 404 | An attachment, or it isn't uploaded yet |
RAVEN_STREAM_NOT_FOUND | 404 | A live stream |
RAVEN_RTC_SERVER_NOT_FOUND | 404 | A media server with that name |
Conflicts
The request is valid, but clashes with something that already exists. The most common one: two users join at once and both try to create the same room.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_CONFLICT | 409 | The name is taken, or the thing is already in that state | Use getOrCreate, or a different name |
RAVEN_MESSAGE_ALREADY_EXISTS | 409 | A clientMessageId was sent twice | Nothing to fix. The retry was caught. Treat it as success |
RAVEN_CONVERSATION_ARCHIVED | 409 | The conversation is archived. You can read but not write | Unarchive it first |
RAVEN_STREAM_INVALID_STATE | 409 | Not allowed in the stream's current state (like starting a stream that's already LIVE, or anything on an ENDED one) | Check the status first. ENDED is final |
RAVEN_IDEMPOTENCY_KEY_IN_USE | 409 | Another request with the same Idempotency-Key is still running. Includes retryAfterSeconds | Wait that many seconds and retry with the same key. You get the first request's response |
Bad requests
The request itself is wrong: a typo in a field name, a value out of range, or a body that's too big. Fix the request; retrying it unchanged won't help.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_VALIDATION_FAILED | 400 | The body or query is wrong. Unknown fields are rejected too, not ignored | Check spelling against the parameter tables |
RAVEN_INVALID_CURSOR | 400 | The pagination cursor can't be read | Start paging again from the beginning, on purpose |
RAVEN_PAYLOAD_TOO_LARGE | 413 | The request is too big | Send less |
RAVEN_MESSAGE_TOO_LARGE | 413 | Message text or metadata is too long | Limit is 4000 characters / 4096 bytes by default |
RAVEN_ATTACHMENT_TOO_LARGE | 413 | The file is bigger than STORAGE_MAX_ATTACHMENT_BYTES | Limit is 25 MB by default |
RAVEN_RATE_LIMITED | 429 | Too many requests. Includes retryAfterSeconds | Wait that many seconds, then retry |
MESSAGE_TOO_LARGE and ATTACHMENT_TOO_LARGE are separate codes so you
know which limit you hit.
RTC tokens
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_RESERVED_IDENTITY | 422 | participantIdentity starts with a prefix Livqeno reserves for its own participants (egress-, relay-, capture-, transcriber-, agent-). The response's prefix names it | Use a different identity, for example prefix your own ids with user- |
Usage limits
Your account has hit one of its free allowances or free-tier limits. You'll usually see these when your backend mints a token or creates a stream.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_USAGE_LIMIT_EXCEEDED | 403 | One of your three free allowances (RTC minutes, chat messages, live-streaming host-hours) is used up | Permanent. Waiting won't help. Move to a paid plan |
RAVEN_PROJECT_LIMIT_EXCEEDED | 403 | A free-plan account already owns 3 active projects | Temporary. Archive or delete a project you no longer use, or move to a paid plan |
RAVEN_STREAM_CONCURRENCY_LIMIT_EXCEEDED | 403 | Your account already has a LIVE stream. The free tier allows one at a time | Temporary. End the other stream, or wait for it to end |
RAVEN_STREAM_VIEWER_LIMIT_EXCEEDED | 403 | The stream has the maximum number of viewers for the free tier | Temporary. Try again when a viewer leaves |
RAVEN_BILLING_SPEND_CAP_REACHED | 403 | Paid plan: this month reached your spend cap | Raise or clear the cap in the dashboard under Billing |
RAVEN_BILLING_ACCOUNT_SUSPENDED | 403 | Paid plan: an invoice is unpaid past its grace period | Pay it in the dashboard under Billing |
The last two are separate from USAGE_LIMIT_EXCEEDED so you can tell
"wait" apart from "nothing left". A paid plan lifts all three.
Infrastructure
Something outside your request went wrong.
| Code | HTTP | What it means | How to fix |
|---|---|---|---|
RAVEN_CONNECTION_FAILED | — | A realtime connection couldn't be made | See RTC troubleshooting |
RAVEN_NO_RTC_CAPACITY | — | No media server had room | A Livqeno-side problem, not yours. Try again later |
RAVEN_WEBHOOK_FAILED | — | A webhook delivery failed | Check the deliveries endpoint |
RAVEN_NOT_CONFIGURED | 501 | This feature isn't turned on. Usually attachments with no STORAGE_BUCKET | A platform-side fix |
RAVEN_INTERNAL_ERROR | 500 | Something unexpected went wrong | Report it with the requestId |
Errors from the server SDK itself
@livqeno/server and livqeno raise these before a request is
sent, so they never appear in an HTTP response. They use the same RAVEN_*
names, so one switch handles everything:
| Code | What it means |
|---|---|
RAVEN_INVALID_CONFIG | No config object, or no apiKey |
RAVEN_TIMEOUT | The request took too long (10 seconds by default) |
RAVEN_NETWORK_ERROR | The request never reached Livqeno |
RAVEN_UNKNOWN_ERROR | Nothing more specific is known |
If a proxy returns an HTML error page instead of JSON, the SDK picks the
code from the HTTP status. It uses the same code the API would have
sent. For example, a 401 is always RAVEN_AUTH_ERROR.
legacyCode and old code
Before RAVEN_* codes existed, code held short values like NOT_FOUND,
UNAUTHORIZED, and VALIDATION_FAILED. Every error now has both:
if (error.code === 'NOT_FOUND') { … } // old — now error.legacyCode
if (error.code === 'RAVEN_ROOM_NOT_FOUND') { … } // newSeveral new codes map to the same old one, so the new codes tell you more.
legacyCode is old and will be removed. Switch to code.
RTC SDK errors
These come from the RTC SDK in your user's browser or phone app: either
thrown by join() and device calls like enableCamera(), or delivered to
room.on('error'). @livqeno/rtc only ever throws or emits RTCError.
Never a raw browser DOMException.
| Code | What it means | How to fix |
|---|---|---|
INVALID_TOKEN | The token is broken, or token / endpoint is missing | Pass the token response to the client unchanged |
TOKEN_EXPIRED | The token had already expired when the client was created | Make the token right before joining, not when the page loads |
ROOM_NOT_FOUND | join() got a room the token wasn't made for | Use the room id or name from the token response |
PERMISSION_DENIED | The token doesn't allow this | Fix the permissions when making the token |
CAMERA_PERMISSION_DENIED | The user blocked camera access | Explain in your own UI. The OS won't ask twice |
MICROPHONE_PERMISSION_DENIED | The user blocked microphone access | Same as above |
DEVICE_NOT_FOUND | The device doesn't exist, or was unplugged | List devices again with getDevices() |
NETWORK_ERROR | A network failure | Check the connection and retry |
SIGNALING_ERROR | The signaling handshake didn't finish | Check endpoint is reachable and uses wss:// |
MEDIA_ERROR | A media action failed | Worth retrying |
INVALID_ARGUMENT | An argument broke a documented limit: a sendData() payload, topic or destination list that is too big, or metadata or an attribute over its size limit. The same code on every platform | Fix the call. Retrying the same arguments fails again |
NOT_SUPPORTED | The device can't do this at all (like screen sharing with no getDisplayMedia) | Permanent. Hide the button. Don't retry |
CONNECTION_FAILED | The connection (ICE/DTLS) couldn't be made | Usually TURN. See troubleshooting |
TIMEOUT | Something took too long | Retry |
NOT_SUPPORTED means "never on this device". MEDIA_ERROR means "try again".
Handle them in your app code, and show the user something useful:
import { isRTCError } from '@livqeno/rtc';
room.on('error', (error) => {
if (!isRTCError(error)) return;
if (error.code === 'CAMERA_PERMISSION_DENIED') showPermissionHelp();
if (error.code === 'NOT_SUPPORTED') hideFeature();
});RTC error categories
You won't catch these in code. You'll see them when investigating a failed
call afterwards. When a client reports a failure, Livqeno gives it a category to help you
debug. You see these in the dashboard, in livqeno errors list, and in the
observability API. They're never returned as a response code.
| Category | What it means |
|---|---|
AUTHENTICATION_ERROR | The credential was rejected |
AUTHORIZATION_ERROR | Logged in, but not allowed |
TOKEN_ERROR | The RTC token was invalid, broken, or expired |
SIGNALING_ERROR | The signaling handshake didn't finish |
ICE_ERROR | The connection checks failed. Usually a firewall or NAT |
TURN_ERROR | The TURN relay couldn't be reached |
SFU_ERROR | The media server couldn't finish, for no more specific reason |
NETWORK_ERROR | A general network failure or timeout |
CLIENT_ERROR | A problem on the device: wrong room, device permission, media error |
UNKNOWN_ERROR | Nothing more specific is known |
How CONNECTION_FAILED is categorized, checked in this order:
- A
turn_unreachablehint →TURN_ERROR - A failed
iceConnectionState→ICE_ERROR - Signaling never became stable →
SIGNALING_ERROR - Anything else →
SFU_ERROR
Each categorized error has a likelyCause and a suggestedAction. They're
worded as "likely", because the server can't be certain.
Chat errors
These come from chat: the chat SDK in your user's app, the chat WebSocket,
or chat REST calls. @livqeno/chat has 26 codes. The server can send 22 of them in an error
frame. Each code maps to one SDK error class, so you can check the class
instead of comparing strings.
| Code | HTTP | SDK class | What it means |
|---|---|---|---|
INVALID_TOKEN | 401 | LivqenoChatAuthenticationError | The token is missing, broken, or signed wrong |
TOKEN_EXPIRED | 401 | LivqenoChatAuthenticationError | Make a new token. Handle onTokenExpiring |
TOKEN_REVOKED | 401 | LivqenoChatAuthenticationError | The token was revoked before it expired |
UNAUTHORIZED | 401 | LivqenoChatAuthenticationError | No usable credential was sent |
PERMISSION_DENIED | 403 | LivqenoChatPermissionError | The token's scope or the user's role doesn't allow this |
NOT_A_MEMBER | 403 | LivqenoChatPermissionError | Not a member of that conversation. Only an API key sees this; a chat token gets ROOM_NOT_FOUND, so conversation names stay hidden |
ORIGIN_NOT_ALLOWED | 403 | LivqenoChatPermissionError | The page's Origin isn't on the project's Allowed Origins list. Add it under Settings → Security → Allowed Origins |
ROOM_NOT_FOUND | 404 | LivqenoRoomError | No such conversation in this project |
NOT_IN_ROOM | 400 | LivqenoRoomError | This connection hasn't joined that conversation |
TOO_MANY_SUBSCRIPTIONS | 400 | LivqenoRoomError | This connection has joined too many conversations |
CONVERSATION_ARCHIVED | 409 | LivqenoRoomError | Archived. You can read but not write |
MESSAGE_NOT_FOUND | 404 | LivqenoMessageError | No such message |
MESSAGE_DELETED | 409 | LivqenoMessageError | The message was deleted |
MESSAGE_TOO_LARGE | 413 | LivqenoMessageError | Text, metadata, or frame is too big |
INVALID_MESSAGE | 400 | LivqenoMessageError | Broken, or missing a required field |
INVALID_MESSAGE_TYPE | 400 | LivqenoMessageError | Frame or message type isn't supported |
INVALID_CURSOR | 400 | LivqenoMessageError | The pagination cursor is broken |
RATE_LIMITED | 429 | LivqenoRateLimitError | Too many requests. Includes retryAfterSeconds |
ATTACHMENT_NOT_FOUND | 404 | LivqenoAttachmentError | No such attachment |
ATTACHMENTS_NOT_CONFIGURED | 501 | LivqenoAttachmentError | File storage isn't turned on |
ATTACHMENT_TOO_LARGE | 413 | LivqenoAttachmentError | Bigger than STORAGE_MAX_ATTACHMENT_BYTES |
CONNECTION_FAILED | — | LivqenoChatConnectionError | Couldn't connect, or ran out of reconnect attempts |
CONNECTION_CLOSED | — | LivqenoChatConnectionError | The connection closed before the server replied |
NETWORK_ERROR | — | LivqenoChatConnectionError | The request never reached Livqeno |
TIMEOUT | — | LivqenoChatConnectionError | No reply in time |
INTERNAL_ERROR | 500 | LivqenoChatError | Something went wrong on the server. It's logged in full |
Chat WebSocket close codes
When the chat connection closes, the close code tells the app whether reconnecting makes sense. The SDK reads it for you; you only need this table if you're debugging or using the raw WebSocket.
| Code | What it means | Reconnect? |
|---|---|---|
1000 | Normal close | No |
4401 | Login failed | No. Retrying won't help |
4403 | Origin not allowed | No. Add it to the project's Allowed Origins |
4429 | Too many connection attempts | Yes, after waiting |
4440 | Token expired | Yes, with a new token |
4500 | Server shutting down | Yes, after waiting. It's a deploy, not a fault |
@livqeno/chat stops on 4401 and 4403 and reports failed, instead of
retrying forever.
Signaling errors
Signaling is the WebSocket an app opens to join a call; it's how the app
and the media server agree how to connect. You'll only handle these codes
directly if you speak the protocol yourself. With the SDK, you'll usually see
them turned into an RTCError, or in logs.
The RTC signaling WebSocket has its own error codes, because it is a
separately versioned protocol. You see them in an error frame. The SDK
turns most of them into an RTCError.
| Code | What it means | Retry? |
|---|---|---|
INVALID_TOKEN | The token is broken or signed wrong | No |
TOKEN_EXPIRED | The token expired before or while connecting | Yes, with a new token |
TOKEN_REVOKED | Someone revoked this token on purpose. Sent at connect, and to a session already running on it (which then ends) | No. Ask your backend for a new one |
TOKEN_INVALID | A token.refresh was refused (bad token, or one for a different room or identity). The session keeps going on its current token | Yes, with another token, before the current one expires |
UNAUTHORIZED | No usable credential | No |
ROOM_NOT_FOUND | No such room in this project and environment | No |
ROOM_FULL | The room has SIGNALING_MAX_PARTICIPANTS_PER_ROOM people | No |
ROOM_CLOSED | The room was closed while you were in it (DELETE /v1/rooms/:id, or its live stream ended) | No. A new token won't get back in |
INVALID_MESSAGE | A broken frame | No |
INVALID_MESSAGE_TYPE | A frame type that isn't supported | No |
PARTICIPANT_NOT_FOUND | That participant isn't in the room | No |
NOT_IN_ROOM | Tried to do something before joining | No |
PERMISSION_DENIED | The token doesn't allow this (like publishing without publish) | No |
ORIGIN_NOT_ALLOWED | The page's Origin isn't on the project's Allowed Origins list | No. Add it under Settings → Security → Allowed Origins |
RATE_LIMITED | Too many messages on this connection | Yes, after waiting |
USAGE_LIMIT_EXCEEDED | The account has used all its included minutes. Calls already running are never cut off | No. Waiting won't help |
NO_RTC_CAPACITY | No media server had room | A Livqeno-side problem |
RTC_SERVER_UNREACHABLE | The chosen media server couldn't be reached | Yes |
NEGOTIATION_FAILED | Setup failed and can't recover | No. Rejoin |
NEGOTIATION_GLARE | An offer is already on its way | Yes. Answer the incoming offer, then retry |
The full message format is in Signaling protocol.
Close codes
These are the RTC signaling close codes (40xx). Don't confuse them with
the chat close codes above (44xx).
When the server closes the signaling connection, it uses one of these codes.
Where it applies, it sends an error frame first.
| Code | Why | What to do |
|---|---|---|
1012 | The server is restarting (a deploy) | Reconnect after a random 0–2 s, resuming the session. The SDKs do this for you |
4001 | Login failed: bad, expired, or revoked token, or the Origin isn't allowed. Also sent when a session's token expires mid-call and the server enforces expiry | Get a new token (or fix Allowed Origins). Don't retry the same one. With refreshToken set, the SDKs refresh and reconnect for you |
4002 | Replaced: a newer connection joined as the same participant | Nothing. The newer tab or device has the session now |
4003 | The participant was removed by your backend, or the room was closed (DELETE /v1/rooms/:id, or its live stream ended) | Leave. Don't reconnect. The SDKs report disconnected with reason REMOVED or ROOM_CLOSED |
4004 | The media server for this session went away | Reconnect. You'll get a healthy server |
4029 | Too many connection attempts | Wait, then reconnect |
Next steps
- Troubleshooting: find a fix by symptom instead of code.
- Limits & quotas · Conventions