Errors¶
Every failed message is answered with an error envelope:
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"version": "1.0",
"source": "dichit-backend",
"timestamp": "2026-08-05T10:00:00.000Z",
"sequence": 45219,
"data": {
"code": "FORBIDDEN",
"message": "You are not authorized to perform this WebSocket action."
},
"meta": {
"traceId": "trc_01HQ5BXWYP1Y5RX0W5X0W5X0W5X"
}
}
idechoes the failing request'sidwhen the envelope could be parsed; a server-generated id is used otherwise.data.messageis a safe, client-facing description — never a stack trace or an internal identifier.data.codeis the stable machine-readable error code.sequenceis the server-assigned per-connection sequence (see protocol.md).- 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. |
SUBSCRIBER_NOT_ELIGIBLE | Subscriber lacks auction participation. | 403 | No | Room entry or staff floor admission for an ineligible enrollment. |
ONLINE_PARTICIPATION_REQUIRED | Online bid while durably floor-backed. | 400 | No (confirm the floor→online switch, then retry with the same command id) | auction.bid.place with source: FLOOR; data.details carries currentSource/requiredSource. |
FLOOR_PARTICIPATION_REQUIRED | Staff floor bid while durably online. | 400 | No (switch to floor participation first) | Staff auction.bid.place with origin: FLOOR and source: ONLINE. |
BID_NOT_IMPROVING, BID_INCREMENT_TOO_SMALL, BID_DECREMENT_TOO_SMALL | Bid violates the mode improvement/step policy. | 400 | No (submit an improving amount) | auction.bid.place failing assertValidBidPolicy; the code arrives as the error message. |
INVALID_AMOUNT | Amount outside program min/max or non-positive. | 400 | No (submit an in-range amount) | auction.bid.place failing bounds validation; the code arrives as the error message. |
AUCTION_ENDED, AUCTION_NOT_LIVE | Bidding window is closed or not live. | 400 | No | auction.bid.place outside the live window. |
ACTIVE_PREBID_EXISTS | An active prebid already exists. | 400 | No (cancel it first) | Duplicate prebid placement. |
PREBID_DISABLED, PREBID_NOT_OPEN | Prebid not enabled or window closed. | 400 | No | Prebid placement outside its window. |
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¶
The examples below omit the envelope boilerplate (version, source, timestamp, sequence, meta) for brevity; those fields are always present on the wire.
BAD_REQUEST — invalid payload¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "BAD_REQUEST",
"message": "Invalid WebSocket message."
}
}
An unknown event type also returns BAD_REQUEST:
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "BAD_REQUEST",
"message": "Unsupported WebSocket event type: auction.dance"
}
}
UNAUTHORIZED¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "UNAUTHORIZED",
"message": "Authentication is required."
}
}
FORBIDDEN — permission denied¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "FORBIDDEN",
"message": "You are not authorized to perform this WebSocket action."
}
}
Room denial:
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "FORBIDDEN",
"message": "Only admins can join the admins room."
}
}
NOT_FOUND¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "NOT_FOUND",
"message": "Auction not found."
}
}
CONFLICT — state conflict¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "CONFLICT",
"message": "State conflict."
}
}
RATE_LIMITED¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"code": "RATE_LIMITED",
"message": "Too many WebSocket messages."
}
}
INTERNAL¶
{
"id": "req_01HQ5BXWYP1Y5RX0W5X0W5X0W5X",
"type": "error",
"data": {
"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.