Error Reference

Complete reference for errors returned by the NoLag REST API and WebSocket connections.

REST API Errors

Error Format

REST error responses come in two shapes, depending on which layer raised the error. Read message in both cases.

Application errors (unknown resource, duplicate slug, invalid identifier, bad credentials) carry a unique id you can quote to support:

{
  "id": "uuid",
  "message": "Human-readable error description",
  "timestamp": "2024-01-15T12:00:00Z"
}
FieldTypeDescription
idstring (UUID)Unique identifier for this error instance. Include this when contacting support.
messagestringHuman-readable description of what went wrong.
timestampstring (ISO 8601)When the error occurred.

Framework errors (a key that is not project-scoped, an app outside your project, a plan limit) use the standard NestJS shape, with no id or timestamp:

{
  "statusCode": 403,
  "message": "API key must be project-scoped to access this endpoint",
  "error": "Forbidden"
}

Validation errors are a third variant of the framework shape: a 400 with "message": "Validation failed" and an errors array, one entry per failed constraint (path, value, message). Unknown body fields fail validation rather than being ignored.

HTTP Status Codes

StatusNameWhen
400Bad RequestValidation failed, invalid UUID, unknown body field, room cap reached, static room deletion, scope from another project
401UnauthorizedMissing or invalid API key/token
403ForbiddenAPI key is not project-scoped, plan app limit reached, rooms/ensure on an app without config.autoProvisionRooms
404Not FoundResource does not exist, has been deleted, or belongs to another project
409ConflictDuplicate slug, duplicate room grant, deleting a scope that still has actors

Response Examples

400 Bad Request

Returned when the request fails validation or contains malformed data.

{
  "id": "01939f83-8b57-7c3e-a456-426614174001",
  "message": "Invalid appId UUID format",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Other 400 messages you will meet: Invalid actorId: must be a UUID or public keyId (at_...), App <appId> has reached its room cap (1000), Cannot delete static rooms created from Blueprint. Disable them instead., Access scope <id> does not belong to this project, Either actorTokenId or actorType must be provided.

401 Unauthorized

Returned when the API key is missing, malformed, or no longer valid.

{
  "id": "01939f83-8b57-7c3e-a456-426614174002",
  "message": "Invalid or expired API key",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Possible message values: Authorization header not provided, Invalid or expired API key, Token type not allowed for this route (a user session token was sent to an API-key route).

403 Forbidden

Returned when the credentials are valid but lack permission to access the resource.

{
  "statusCode": 403,
  "message": "API key must be project-scoped to access this endpoint",
  "error": "Forbidden"
}

Other 403 messages: App limit reached. Please upgrade your subscription to create more apps. and, for POST /apps/{appId}/rooms/ensure, App <appId> does not allow dynamic room provisioning (set config.autoProvisionRooms=true to enable). Rooms must be created explicitly; unknown rooms are rejected at the broker.

404 Not Found

Returned when the requested resource does not exist, has been deleted, or belongs to a different project than the API key.

{
  "statusCode": 404,
  "message": "App 01939f83-8b57-7c3e-a456-426614174000 not found in project 019e8d1e-a59b-748b-9f69-dd03a91400f8",
  "error": "Not Found"
}

Other 404 messages: Actor token <id> not found, Actor token <id> not found in this project (grant to an actor from another project), Access scope <id> not found, Room grant <id> not found.

409 Conflict

Returned when a create or update would violate a uniqueness constraint.

{
  "id": "01939f83-8b57-7c3e-a456-426614174005",
  "message": "Room with slug \"general\" already exists",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Other 409 messages: App with slug "<slug>" already exists in this project (framework shape), A scope with slug "<slug>" already exists in this project, That actor already has a grant on this room. Update it instead., Cannot delete scope: <n> actor(s) are still assigned to it. Unscope them first.

WebSocket Errors

Error Format

WebSocket errors are delivered as error frames on the open connection (MessagePack encoded, shown here as JSON):

{
  "type": "error",
  "error": "unknown_topic",
  "code": 42940,
  "topic": "my-app-a3f9/general/messages",
  "hint": "room is not configured. Provision it via the control-plane rooms API before use."
}
FieldTypeDescription
typestringAlways "error".
errorstringMachine-readable error name.
codenumberNumeric error code. Present on rate, quota, size and unknown-topic errors.
topicstringOptional. Included when the error relates to a specific topic.
hintstringOptional. Human-readable remediation, currently sent with unknown_topic.
msgRefstringOptional. Echoes the msgRef of the publish frame that failed, so a client can settle the matching publish callback.
lobbyIdstringOptional. Included instead of topic on lobby errors.
maxSizeBytesnumberOptional. Included with message_too_large.

code, hint and msgRef are protocol v2 fields. Send "protocolVersion": 2 in the auth frame (every current SDK does) to receive them; a v1 connection gets the legacy not_authorized frame for a denied topic and never receives published acks.

Error Codes

CodeErrorDescription
42910rate_limit_exceededMore than 50 publishes per second on this connection.
42920monthly_quota_exceededProject has exceeded its monthly message quota.
42930message_too_largePayload exceeds the 900 KB ceiling (921,600 bytes, the same on every plan). The frame includes maxSizeBytes.
42940unknown_topicSubscribe or publish to a room that does not exist or that this actor may not access. Rooms are never created implicitly; create them via the REST API first. Carries hint.
-not_authorizedLobby access denied (lobbyId set). Also the v1 fallback for a denied topic on connections that did not send protocolVersion: 2.
-not_subscribedsetFilters on a topic this connection has not subscribed to.
-invalid_filter_chars (/, #, +, | not allowed)A filter contains /, #, + or |, is an empty string, or an AND-group has fewer than two entries. The wire string includes the parenthetical.
-too_many_filters (max 100)More than 100 filters specified for a topic. The wire string includes the parenthetical.
-not_authenticatedAny frame other than auth sent before authentication completed.

Close Codes

The server may close the WebSocket connection with a specific close code:

CodeReasonDescription
4001token_revoked, token_expired, token_not_found, project_not_found, scope_inactivePeriodic revalidation (about every 10 minutes) rejected the actor. The reason string is the close reason. No disconnect frame precedes this close.
4002connection_limit_reachedThe organization went over its concurrent connection limit after a plan change, detected at revalidation. At connect time the same condition is reported as an auth failure frame, not a close.
4003token_expiredA client token (JWT) passed its exp, detected at the next heartbeat. Preceded by a disconnect frame.

A silent connection is also closed by the server's 60-second idle timeout; send a heartbeat (empty binary frame) at least that often.

Server-Initiated Disconnect

Before a 4003 close the server sends a disconnect frame. This is the only reason the frame carries today:

{
  "type": "disconnect",
  "reason": "token_expired"
}

Renew the client token with a reauth frame before it expires to avoid this; see WebSocket API.

Authentication Errors

If authentication fails, the server responds with an auth frame indicating the failure and leaves the socket open; the client may retry with another token or close.

{
  "type": "auth",
  "success": false,
  "error": "access_denied"
}

Possible error values:

  • "access_denied": the control plane rejected the token. Covers an unknown, disabled, expired or cross-project actor, a client token with a bad signature, and an actor whose scope is inactive, deleted or belongs to another project (scope_inactive on the control plane; the broker does not forward the specific reason at connect).
  • "authentication_failed": the control plane returned an unexpected status.
  • "connection_failed": the broker could not reach the control plane.
  • "token_expired": a client token (JWT) whose exp has already passed.
  • "connection_limit_reached": the organization's concurrent connection limit is full.
  • "broker_unavailable": the broker's internal message bus is unreachable.

Re-authentication Errors

A reauth frame (used to renew a client token on the live connection) is answered with { "type": "reauth", "success": false, "error": ... } when it fails. The connection stays open under its current authentication until that expires.

  • "actor_mismatch": the new token resolves to a different actor than the connection was opened with.
  • "token_expired": the new token has already expired.
  • Any of the authentication error strings above when the control plane rejects the token.

Next Steps