Skip to content
Last updated

Errors

WebSocket errors fall into two categories depending on when they occur: before or after the HTTP-to-WebSocket upgrade.

Pre-Upgrade Errors (HTTP)

Authentication, rate limiting, and connection limit checks happen before the WebSocket connection is established. These are returned as plain HTTP responses.

The X-WS-Error response header contains a short description useful for clients that don't parse the response body (e.g. Postman's WebSocket view).

HTTP StatusCause
401Missing or invalid API key
429Rate limit exceeded, or per-API-key connection limit (10) reached
503Connection limit reached

Post-Upgrade Connection Race

In rare cases where two connections from the same API key complete the HTTP upgrade simultaneously, one will be rejected after the upgrade. In this race condition the server closes the socket immediately with WebSocket close code PolicyViolation — no error message is sent. Your client should treat a PolicyViolation close as equivalent to a connection limit error and wait before reconnecting.

Example — invalid API key:

curl -i \
  -H "Authorization: Bearer INVALID_KEY" \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://api4-general.collective2.com/ws
HTTP/1.1 401 Unauthorized
X-WS-Error: Invalid API key

{"code":"UNAUTHORIZED","message":"Invalid API key"}

Post-Upgrade Errors (WebSocket Messages)

After the connection is established, errors are delivered as error messages:

{
  "msg_type": "error",
  "id": "msg_a1b2c3d4e5f6a1b2c3d4e5f6a7b8",
  "timestamp": "2024-01-01T12:00:00Z",
  "v": 4,
  "payload": {
    "code": "FORBIDDEN",
    "field": "StrategyId",
    "message": "Not authorized to subscribe to Strategy"
  }
}

When multiple validation errors occur simultaneously, the errors array is populated instead of the top-level code, field, and message fields:

{
  "msg_type": "error",
  "id": "msg_a1b2c3d4e5f6a1b2c3d4e5f6a7b8",
  "timestamp": "2024-01-01T12:00:00Z",
  "v": 4,
  "payload": {
    "errors": [
      {
        "code": "MANDATORY_PARAM_EMPTY_OR_MALFORMED",
        "field": "payload.channel",
        "message": "Missing parameter"
      },
      {
        "code": "INVALID_PARAMETER",
        "field": "payload.strategy_id",
        "message": "Invalid parameter"
      }
    ]
  }
}

Error Codes

CodeDescriptionRetryable
TOO_MANY_REQUESTSRate limit exceeded. Sent as an error message — the connection stays open.Yes, after a delay
TOO_MANY_CONNECTIONSPer-API-key connection limit (10) reached. Returned as HTTP 429 pre-upgrade, or as a PolicyViolation socket close in a concurrent connection race post-upgrade.Yes, after closing another connection
FORBIDDENNot authorized to receive signals for this strategy. You must own or be subscribed to the strategy to receive its signals.No
TOO_MANY_SUBSCRIPTIONSChannel limit reached on this connection.No — unsubscribe first
TOO_MANY_REQUESTSRate limit exceeded. Sent as an error message — the connection stays open.Yes, after a delay
SERVICE_UNAVAILABLEServer-wide connection limit reached. Returned as HTTP 503 pre-upgrade.Yes, with backoff
ALREADY_EXISTSAlready subscribed to this channel and strategy.No
NOT_FOUNDTried to unsubscribe from a channel not currently subscribed.No
MANDATORY_PARAM_EMPTY_OR_MALFORMEDA required field is missing or empty.No
INVALID_PARAMETERA field is present but invalid (e.g. unknown msg_type, clock-skewed timestamp).No
FRAGMENTEither: a WebSocket-level fragmented message was sent, or the message body was not valid JSON.No
UNKNOWNUnexpected server error.No. Contact API support

Rate Limiting

The WebSocket API shares the same rate limiter as the REST API.

Important distinction: At connection time, a rate limit violation returns HTTP 429 and rejects the upgrade. After the connection is established, a rate limit violation sends an error message with TOO_MANY_REQUESTS and keeps the connection open — you do not need to reconnect.

Slow Client Eviction

If your client processes messages too slowly, the server will close the connection with WebSocket close code NormalClosure and close reason "Ping timeout".

This happens when the server's outbound queue for your connection is non-empty and the last successful send to your client was more than 3 seconds ago. The eviction check runs every 10 seconds. Despite the close code being NormalClosure, this is an eviction — not a clean shutdown initiated by you.

To avoid eviction:

  • Process each incoming message quickly; offload heavy work to a background queue
  • Do not block the receive loop with database writes, HTTP calls, or slow computation
  • If you are routinely evicted, your consumer is too slow relative to signal publish rate

When evicted, reconnect and resubscribe with resume_from set to your last seq_id to recover any missed signals — see Gap Fill.

Reconnection Guidance

SituationWhat to do
FORBIDDENStop — you are not authorized for this strategy
TOO_MANY_REQUESTSWait and retry the message — do not reconnect
SERVICE_UNAVAILABLEReconnect with exponential backoff
TOO_MANY_CONNECTIONSClose an existing connection before opening a new one
Connection closed unexpectedlyReconnect with resume_from set to your last seq_id — see Gap Fill
NormalClosure with reason "Ping timeout"Client eviction — optimize your processing loop, then reconnect with resume_from
UNKNOWNContact API support