Skip to content

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"
  }
}
  • id echoes the failing request's id when the envelope could be parsed; a server-generated id is used otherwise.
  • data.message is a safe, client-facing description — never a stack trace or an internal identifier.
  • data.code is the stable machine-readable error code.
  • sequence is 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_LIMITED and INTERNAL may be retried after backoff.
  • BAD_REQUEST, FORBIDDEN, NOT_FOUND, CONFLICT should not be retried as-is; fix the request first.
  • UNAUTHORIZED requires 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.