Every Struct surface returns errors in a consistent shape. This page covers the wire format for REST and WebSocket. The TypeScript SDK exposes a typed hierarchy on top, documented in SDK Errors.
REST
Failed REST requests return the same envelope as successful ones, with success: false and a human-readable message. The HTTP status reflects the error class.
{
"success": false,
"data": null,
"message": "Invalid API key"
}
Status codes
| Status | Meaning | Common causes |
|---|
400 Bad Request | Invalid parameters. | Missing required query string, malformed condition_id, unsupported candle resolution. |
401 Unauthorized | Missing or invalid credentials. | X-API-Key header absent, key disabled, or JWT signature invalid. |
403 Forbidden | Auth succeeded but the key is over its limits. | Org out of credits, key revoked, or quota exceeded. |
404 Not Found | Resource does not exist. | Market, event, or trader not indexed (yet) or wrong identifier. |
429 Too Many Requests | Rate limit exceeded. | See Rate Limits. |
500 Internal Server Error | Unexpected server failure. | Transient. Retry with backoff. |
502, 503, 504 | Upstream or timeout. | Transient. Retry with backoff. |
On 429, the response includes Retry-After (seconds) when applicable. Honour it before retrying.
Retrying
Any 5xx, 429, network failure, or timeout is retryable. Use exponential backoff with jitter; never tight-loop. The TypeScript SDK does this automatically when retry is configured.
WebSocket
In-band errors
Invalid messages on either the rooms (/ws) or alerts (/ws/alerts) endpoint return a JSON error frame instead of closing the connection:
{
"error": "unknown event type"
}
{
"type": "error",
"message": "filter limit exceeded"
}
These cover protocol-level problems: unknown room IDs, malformed subscribe payloads, filter limits, and similar. The socket stays open; you can correct the message and retry.
Close codes
When the server closes the connection, it uses a standard WebSocket close code:
| Code | Reason | Action |
|---|
1000 | Normal closure. | Client disconnected cleanly. No action. |
1001 | Server going away (restart, deploy). | Reconnect with backoff. |
1008 | Policy violation. Usually auth failure. | Fix credentials before reconnecting. Do not retry blindly. |
1009 | Message too large. | Reduce filter size or split into multiple subscriptions. |
1011 | Server error. | Reconnect with backoff. |
4001 | Authentication failed. | Check api-key and token query parameters. |
4002 | Connection limit reached. | Increase your plan or close idle sockets. See Rate Limits. |
4003 | Ping timeout (no client activity within keepalive window). | Send pings every 30s. |
4004 | Out of credits. | Top up credits in the dashboard. |
Reconnection
When the socket drops for any non-auth reason, reconnect with exponential backoff and resubscribe to every room you were previously in. Subscriptions are not persisted server-side; they live only for the lifetime of the connection.
The TypeScript SDK handles reconnect and resubscribe automatically. See SDK WebSockets.
let attempt = 0;
function connect() {
const ws = new WebSocket("wss://api.struct.to/ws?api-key=YOUR_API_KEY");
ws.onopen = () => {
attempt = 0;
resubscribe(ws);
};
ws.onclose = (event) => {
if (event.code === 1008 || event.code === 4001) return;
const delay = Math.min(30_000, 1_000 * 2 ** attempt);
attempt += 1;
setTimeout(connect, delay + Math.random() * 500);
};
}
Webhooks
Webhook deliveries are retried automatically when your endpoint returns a non-2xx response, with exponential backoff. Endpoints that fail repeatedly are paused; see Webhooks for the full retry schedule.
SDK errors
The TypeScript SDK lifts these wire-level failures into a typed hierarchy:
StructError
├── HttpError (REST: non-2xx response)
├── NetworkError (REST: fetch failed)
├── TimeoutError (REST: request timed out)
└── WebSocketError
└── WebSocketClosedError
See SDK Errors for the full hierarchy, retry behaviour, and example handlers. Last modified on May 27, 2026