Error Handling
This guide covers error response shapes, error codes, and retry strategies for the Synthetix API.
API Error Codes
Every order rejection now includes a machine-readable errorCode field alongside
the human-readable error message. Use errorCode for programmatic handling;
treat error as debug context only.
Response Shapes
Place / Cancel Orders
Trading rejections are per-item inside an HTTP 200 response:
{
"status": "ok",
"response": {
"statuses": [
{
"resting": { "order": { "venueId": "123", "clientId": "0xabc" }, "id": "123" }
},
{
"error": "reduce-only order requires open position",
"errorCode": "REDUCE_ONLY_NO_POSITION",
"order": { "venueId": "0", "clientId": "0xdef" }
}
]
},
"timestamp": 1773351037815
}Modify Order
Rejections are on the response object directly:
{
"status": "ok",
"response": {
"order": { "venueId": "456", "clientId": "0xabc" },
"orderId": "456",
"status": "rejected",
"error": "order 456 not found",
"errorCode": "ORDER_NOT_FOUND",
"timestamp": 1773351037815
},
"timestamp": 1773351037815
}Request Validation Errors
Invalid requests return HTTP 400 with a top-level error (no errorCode per-item):
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"category": "REQUEST",
"message": "orders array cannot be empty",
"retryable": false
},
"timestamp": 1773351037815
}Trading Error Codes
Returned as per-item rejections with HTTP 200.
| Code | Description | Retryable |
|---|---|---|
CANCEL_FAILED | Cancel operation failed at the matching engine | No |
FOK_NOT_FILLED | Fill-or-kill order could not be fully filled immediately | No |
IDEMPOTENCY_CONFLICT | Duplicate clientOrderId | No |
INSUFFICIENT_MARGIN | Not enough margin for the order | No |
INVALID_ORDER_SIDE | Invalid order side | No |
INVALID_TRIGGER_PRICE | Trigger price invalid for order type | No |
IOC_NOT_FILLED | Immediate-or-cancel found no immediate execution | No |
MARKET_CLOSED | Market is not open for trading | No |
MARKET_NOT_FOUND | Market does not exist | No |
MAX_ORDERS_PER_MARKET | Per-market open order cap reached — cancel orders on this market or see Tiers | No |
MAX_TOTAL_ORDERS | Global open order cap reached across all markets — cancel orders or see Tiers | No |
MAX_SUB_ACCOUNTS_EXCEEDED | Subaccount limit reached | No |
NO_LIQUIDITY | Market order found no liquidity on the opposing side | No |
OI_CAP_EXCEEDED | Order would exceed open interest cap | No |
OPERATION_TIMEOUT | Operation timed out | Yes |
ORDER_NOT_FOUND | Order does not exist (cancel/modify) | No |
ORDER_REJECTED_BY_ENGINE | Matching engine rejection with no specific code (see error for details) | No |
POSITION_NOT_FOUND | Position does not exist | No |
POST_ONLY_WOULD_TRADE | Post-only order would take liquidity | No |
PRICE_OUT_OF_BOUNDS | Fill price exceeded configured cap/floor | No |
QUANTITY_BELOW_FILLED | Modified quantity is below already-filled amount | No |
QUANTITY_TOO_SMALL | Quantity below market minimum | No |
REDUCE_ONLY_NO_POSITION | Reduce-only order but no position exists | No |
REDUCE_ONLY_SAME_SIDE | Reduce-only order on the same side as position | No |
REDUCE_ONLY_WOULD_INCREASE | Reduce-only order would increase position size | No |
SELF_TRADE_PREVENTED | Order would match against own resting order | No |
WICK_INSURANCE_ACTIVE | Wick protection period active, try again shortly | Yes |
Request / System Error Codes
Returned with HTTP 4xx/5xx as top-level errors.
| Code | HTTP | Retryable |
|---|---|---|
VALIDATION_ERROR | 400 | No |
MISSING_REQUIRED_FIELD | 400 | No |
INVALID_FORMAT | 400 | No |
INVALID_VALUE | 400 | No |
UNAUTHORIZED | 401 | No |
FORBIDDEN | 403 | No |
NOT_FOUND | 404 | No |
METHOD_NOT_ALLOWED | 405 | No |
RATE_LIMIT_EXCEEDED | 429 | Yes |
INTERNAL_ERROR | 500 | Yes |
DATABASE_ERROR | 500 | Yes |
CACHE_ERROR | 500 | Yes |
UNKNOWN_ERROR | 500 | No |
Retry Policy
Only three codes should be retried:
| Code | Strategy |
|---|---|
WICK_INSURANCE_ACTIVE | Wait a few seconds, retry unchanged |
OPERATION_TIMEOUT | Retry immediately or with short backoff |
RATE_LIMIT_EXCEEDED | Backoff per rate limit headers |
All other codes indicate a condition that won't resolve without changing the request or account state.
Batch Request Error Handling
Batch operations like placeOrders and cancelOrders return HTTP 200 with per-item statuses.
Each item in the statuses array corresponds to the item at the same index in your request.
Key Points
- Order preserved: Status array indices match the order of items in your request
- Independent processing: Each item is validated independently
- No rollback: Successfully processed items are not rolled back if others fail
- Check every item: An HTTP 200 response does not mean all items succeeded
Handling Batch Responses
Always iterate through the statuses array and check for errorCode on each item:
function handleBatchResponse(response) {
if (response.status === 'error') {
// Request-level error - entire batch failed
throw new Error(response.error.message);
}
const results = { succeeded: [], failed: [] };
for (const status of response.response.statuses) {
if (status.errorCode) {
results.failed.push({ errorCode: status.errorCode, error: status.error });
} else if (status.resting) {
results.succeeded.push({ type: 'resting', id: status.resting.id });
} else if (status.filled) {
results.succeeded.push({ type: 'filled', ...status.filled });
} else if (status.canceled) {
results.succeeded.push({ type: 'canceled', id: status.canceled.id });
}
}
return results;
}WebSocket Error Handling
WebSocket connections use a different error format for responses:
{
"id": "request-123",
"requestId": "request-123",
"status": 400,
"timestamp": 1704067200000,
"traceId": "abc123def456",
"error": {
"errorCode": "VALIDATION_ERROR",
"code": 400,
"message": "Invalid order parameters",
"category": "REQUEST",
"retryable": false,
"details": {}
}
}WebSocket error fields:
errorCode- Specific error code for programmatic handlingcategory- Error category (REQUEST, AUTH, RATE_LIMIT, TRADING, SYSTEM)retryable- Whether the operation can be retrieddetails- Additional context about the error
503 Service Unavailable (backlog full)
When the per-connection message processing backlog is full, the server returns HTTP status 503 with message "Server busy, try again". The response now includes the client requestId (or legacy id) parsed from the dropped message, making it easier to correlate the error with a specific client operation.
| Condition | requestId in response |
|---|---|
Valid requestId or id in the dropped message | Echoed in the error response |
| Malformed JSON, invalid ID, or ID exceeding 255 characters | Empty (same as prior behavior) |
When requestId is present, it matches the value sent in the original request. Prefer requestId over legacy id when sending requests.
{
"id": "order-42",
"requestId": "order-42",
"status": 503,
"timestamp": 1719144000000,
"error": {
"code": 503,
"message": "Server busy, try again"
}
}Rate limit snapshot on trade responses
WebSocket trade responses may include a top-level rateLimit object with requestsCap and remainingTokens, including on 429 rate-limit rejections and 503 request timeouts when a snapshot is available. See Rate Limits — Rate Limit Snapshot on Trade Responses.
WebSocket-specific close codes:
- Connection errors result in WebSocket close codes
- Authentication failures close the connection with code 1008
- Protocol errors use code 1002