Skip to content

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.

CodeDescriptionRetryable
CANCEL_FAILEDCancel operation failed at the matching engineNo
FOK_NOT_FILLEDFill-or-kill order could not be fully filled immediatelyNo
IDEMPOTENCY_CONFLICTDuplicate clientOrderIdNo
INSUFFICIENT_MARGINNot enough margin for the orderNo
INVALID_ORDER_SIDEInvalid order sideNo
INVALID_TRIGGER_PRICETrigger price invalid for order typeNo
IOC_NOT_FILLEDImmediate-or-cancel found no immediate executionNo
MARKET_CLOSEDMarket is not open for tradingNo
MARKET_NOT_FOUNDMarket does not existNo
MAX_ORDERS_PER_MARKETPer-market open order cap reached — cancel orders on this market or see TiersNo
MAX_TOTAL_ORDERSGlobal open order cap reached across all markets — cancel orders or see TiersNo
MAX_SUB_ACCOUNTS_EXCEEDEDSubaccount limit reachedNo
NO_LIQUIDITYMarket order found no liquidity on the opposing sideNo
OI_CAP_EXCEEDEDOrder would exceed open interest capNo
OPERATION_TIMEOUTOperation timed outYes
ORDER_NOT_FOUNDOrder does not exist (cancel/modify)No
ORDER_REJECTED_BY_ENGINEMatching engine rejection with no specific code (see error for details)No
POSITION_NOT_FOUNDPosition does not existNo
POST_ONLY_WOULD_TRADEPost-only order would take liquidityNo
PRICE_OUT_OF_BOUNDSFill price exceeded configured cap/floorNo
QUANTITY_BELOW_FILLEDModified quantity is below already-filled amountNo
QUANTITY_TOO_SMALLQuantity below market minimumNo
REDUCE_ONLY_NO_POSITIONReduce-only order but no position existsNo
REDUCE_ONLY_SAME_SIDEReduce-only order on the same side as positionNo
REDUCE_ONLY_WOULD_INCREASEReduce-only order would increase position sizeNo
SELF_TRADE_PREVENTEDOrder would match against own resting orderNo
WICK_INSURANCE_ACTIVEWick protection period active, try again shortlyYes

Request / System Error Codes

Returned with HTTP 4xx/5xx as top-level errors.

CodeHTTPRetryable
VALIDATION_ERROR400No
MISSING_REQUIRED_FIELD400No
INVALID_FORMAT400No
INVALID_VALUE400No
UNAUTHORIZED401No
FORBIDDEN403No
NOT_FOUND404No
METHOD_NOT_ALLOWED405No
RATE_LIMIT_EXCEEDED429Yes
INTERNAL_ERROR500Yes
DATABASE_ERROR500Yes
CACHE_ERROR500Yes
UNKNOWN_ERROR500No

Retry Policy

Only three codes should be retried:

CodeStrategy
WICK_INSURANCE_ACTIVEWait a few seconds, retry unchanged
OPERATION_TIMEOUTRetry immediately or with short backoff
RATE_LIMIT_EXCEEDEDBackoff 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 handling
  • category - Error category (REQUEST, AUTH, RATE_LIMIT, TRADING, SYSTEM)
  • retryable - Whether the operation can be retried
  • details - 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.

ConditionrequestId in response
Valid requestId or id in the dropped messageEchoed in the error response
Malformed JSON, invalid ID, or ID exceeding 255 charactersEmpty (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