Errors¶
Every failed message is answered with an error envelope:
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "FORBIDDEN",
"message": "You are not authorized to perform this WebSocket action."
}
requestIdechoes the failing request's id when the envelope could be parsed.messageis a safe, client-facing description — never a stack trace or an internal identifier.- Error responses are always delivered on the same connection; an error never terminates the connection (connection-level failures use close codes instead, see connection.md).
Reference: protocol.md · connection.md · authentication.md
Error codes¶
The canonical error codes are produced by the platform's central WebSocket error mapper.
| Code | Description | HTTP Equivalent | Retryable | When it occurs |
|---|---|---|---|---|
BAD_REQUEST | Invalid envelope or event payload. | 400 | No | Malformed JSON, unknown event type, Zod validation failure. |
UNAUTHORIZED | Authentication is required / invalid token. | 401 | No (re-authenticate) | Token missing, invalid, expired, or wrong type on the message context. |
FORBIDDEN | Authenticated but not allowed to perform the action. | 403 | No | Missing route permission, room access denied, role restriction. |
NOT_FOUND | The referenced resource does not exist. | 404 | No | Unknown cycleId, bidId, auction. |
CONFLICT | The operation conflicts with current state. | 409 | No | Bid rejected by rules, illegal state transition, duplicate idempotency key in a competing window. |
RATE_LIMITED | Per-user or per-IP limit exceeded. | 429 | Yes (after window reset) | Sliding-window limit exceeded for the event type. |
INTERNAL | Unexpected server failure. | 500 | Yes | Any unhandled error; the server logs details and returns a safe message. |
Retryability¶
RATE_LIMITEDandINTERNALmay be retried after backoff.BAD_REQUEST,FORBIDDEN,NOT_FOUND,CONFLICTshould not be retried as-is; fix the request first.UNAUTHORIZEDrequires a fresh token + reconnection, not a naive retry.
Requested/aliased error codes¶
Some integrations and earlier drafts referenced the following names. They are not emitted by the current server but are part of the historical contract. Map them as follows:
| Legacy name | Canonical code | Notes |
|---|---|---|
INVALID_TOKEN | UNAUTHORIZED | Emitted at handshake close (1008); a message-level error uses UNAUTHORIZED. |
INVALID_PAYLOAD | BAD_REQUEST | Envelope/data validation failure. |
INVALID_BID | BAD_REQUEST / CONFLICT | Invalid bid shape → BAD_REQUEST; rule rejection (min amount, race) → CONFLICT. |
ROOM_NOT_FOUND | NOT_FOUND | Unknown room name; unauthorized room → FORBIDDEN. |
FORBIDDEN | FORBIDDEN | Same code. |
RATE_LIMITED | RATE_LIMITED | Same code. |
INTERNAL_ERROR | INTERNAL | Same semantics. |
Error examples¶
BAD_REQUEST — invalid payload¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "BAD_REQUEST",
"message": "Invalid WebSocket message."
}
An unknown event type also returns BAD_REQUEST:
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "BAD_REQUEST",
"message": "Unsupported WebSocket event type: auction.dance"
}
UNAUTHORIZED¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "UNAUTHORIZED",
"message": "Authentication is required."
}
FORBIDDEN — permission denied¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "FORBIDDEN",
"message": "You are not authorized to perform this WebSocket action."
}
Room denial:
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "FORBIDDEN",
"message": "Only admins can join the admins room."
}
NOT_FOUND¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "NOT_FOUND",
"message": "Auction not found."
}
CONFLICT — state conflict¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "CONFLICT",
"message": "State conflict."
}
RATE_LIMITED¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "RATE_LIMITED",
"message": "Too many WebSocket messages."
}
INTERNAL¶
{
"type": "error",
"requestId": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"code": "INTERNAL",
"message": "An unexpected error occurred."
}
Connection-level failures¶
Failures during the handshake never produce an error envelope; they close the connection with a WebSocket close code:
| Close code | Reason | Meaning |
|---|---|---|
1008 | Authentication failed | Invalid/missing token, or token not an access token. |
1009 | — | Frame larger than the max payload. |
1001 | — | Server shutting down (going away). |
1000 | — | Clean client-initiated close. |
Where errors are raised¶
| Stage | Error codes |
|---|---|
| Envelope parse | BAD_REQUEST |
| Route lookup | BAD_REQUEST |
| Authenticate middleware | UNAUTHORIZED |
| Rate-limit middleware | RATE_LIMITED |
| Validate middleware (Zod) | BAD_REQUEST |
| Authorize middleware | FORBIDDEN |
| Handler (business rules) | BAD_REQUEST, FORBIDDEN, NOT_FOUND, CONFLICT |
| Unhandled | INTERNAL |
See the routing pipeline in protocol.md and the authorization diagram in diagrams.md.
Related documents¶
- protocol.md — the
errorenvelope. - authentication.md —
UNAUTHORIZEDvsFORBIDDEN. - rate-limits.md —
RATE_LIMITEDbehaviour. - connection.md — close codes.