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_EXCEEDED | Open order limit reached | 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
WebSocket-specific close codes:
- Connection errors result in WebSocket close codes
- Authentication failures close the connection with code 1008
- Protocol errors use code 1002