Errors

The API uses standard HTTP status codes and returns JSON error responses with structured error codes.

HTTP Status Codes

CodeMeaning
200Success
400Bad Request — Invalid parameters
401Unauthorized — Missing or invalid auth
403Forbidden — Insufficient scope or IP blocked
404Not Found — Resource doesn't exist
409Conflict — Duplicate or state conflict
429Too Many Requests — Rate limit exceeded
500Internal Server Error
503Service Unavailable — Platform is paused

Error Response Format

All error responses return a JSON object with an error field:

{
  "error": "descriptive error message"
}

Some errors include an additional code field for programmatic handling:

{
  "error": "missing or invalid authentication",
  "code": "UNAUTHORIZED"
}

Global Error Code Catalog

These error codes can be used for programmatic error handling across all endpoints:

CodeHTTP StatusDescription
UNAUTHORIZED401Missing or invalid authentication credentials
API_KEY_UNAUTHORIZED401API key not found, revoked, or expired
FORBIDDEN403Authenticated but not permitted to access resource
INSUFFICIENT_SCOPE403API key lacks the required scope for the endpoint
RATE_LIMITED429Request rate limit exceeded
VALIDATION_ERROR400Request body or parameters failed validation
NOT_FOUND404Requested resource does not exist
INTERNAL_ERROR500Unexpected server error

Example Response Bodies

UNAUTHORIZED (401)

{
  "error": "missing or invalid authentication",
  "code": "UNAUTHORIZED"
}

API_KEY_UNAUTHORIZED (401)

{
  "error": "API key is revoked or expired",
  "code": "API_KEY_UNAUTHORIZED"
}

FORBIDDEN (403)

{
  "error": "access denied",
  "code": "FORBIDDEN"
}

INSUFFICIENT_SCOPE (403)

{
  "error": "API key lacks scope: trade:orders",
  "code": "INSUFFICIENT_SCOPE"
}

RATE_LIMITED (429)

{
  "error": "rate limit exceeded",
  "code": "RATE_LIMITED"
}

VALIDATION_ERROR (400)

{
  "error": "invalid side: must be BUY or SELL",
  "code": "VALIDATION_ERROR"
}

NOT_FOUND (404)

{
  "error": "order not found",
  "code": "NOT_FOUND"
}

INTERNAL_ERROR (500)

{
  "error": "internal server error",
  "code": "INTERNAL_ERROR"
}

Common Errors by Category

Authentication

ErrorHTTPCause
missing required API key headers401One or more HMAC headers missing
invalid timestamp401Timestamp not a valid Unix epoch
timestamp outside acceptable window401Clock skew > 30 seconds
invalid API key401Key not found or revoked
API key is revoked or expired401Key was revoked by admin
invalid passphrase401Passphrase doesn't match
invalid signature401HMAC signature verification failed

Orders

ErrorHTTPCause
platform is paused503Trading is temporarily suspended
market not active400Market is not in active status
invalid side400Side must be BUY or SELL
invalid maker_amount400Non-positive or non-numeric amount
order already expired400Expiration timestamp is in the past
FOK order could not be fully filled400No complete match available (FOK)
FAK order had no immediate fills400No matching orders on the book (FAK)
post-only order would cross the book400Order would take liquidity (post-only)
order cannot be cancelled409Order already filled/cancelled/expired
order not found404Order hash doesn't exist or wrong user

Time-in-Force

ErrorHTTPCause
invalid time_in_force400TIF value not GTC/GTD/FOK/FAK
GTD orders require an expiration time400Missing expiration for GTD

Rate Limits

ErrorHTTPCause
rate limit exceeded429Too many requests per window

Batch Operations

ErrorHTTPCause
too many orders (max 50)400Batch exceeds 50 orders
no orders provided400Empty orders array
no order hashes provided400Empty cancel hashes array

Handling Errors

import requests
import time

resp = requests.post(f"{BASE_URL}/orders", json=order, headers=headers)

if resp.status_code == 429:
    # Rate limited — wait and retry
    reset_time = int(resp.headers.get("X-RateLimit-Reset", 0))
    wait_seconds = max(1, reset_time - int(time.time()))
    time.sleep(wait_seconds)
    # Retry...

elif resp.status_code == 401:
    error = resp.json()
    code = error.get("code", "")
    if code == "API_KEY_UNAUTHORIZED":
        print("API key revoked — regenerate key")
    else:
        print("Auth error — check credentials")

elif resp.status_code == 503:
    # Platform paused — stop trading
    print("Platform is paused, stopping")

elif resp.status_code >= 400:
    error = resp.json().get("error", "unknown error")
    print(f"Error {resp.status_code}: {error}")
const resp = await fetch(`${BASE_URL}/orders`, {
  method: 'POST',
  headers: { ...authHeaders, 'Content-Type': 'application/json' },
  body: JSON.stringify(order),
});

if (resp.status === 429) {
  const resetTime = parseInt(resp.headers.get('X-RateLimit-Reset') ?? '0');
  const waitMs = Math.max(1000, (resetTime - Math.floor(Date.now() / 1000)) * 1000);
  await new Promise((r) => setTimeout(r, waitMs));
  // Retry...
} else if (resp.status === 401) {
  const { code } = await resp.json();
  if (code === 'API_KEY_UNAUTHORIZED') {
    console.error('API key revoked — regenerate key');
  }
} else if (!resp.ok) {
  const { error } = await resp.json();
  console.error(`Error ${resp.status}: ${error}`);
}

Idempotency

Order placement supports idempotency via the EIP-712 order hash. Submitting the same signed order twice returns the existing order instead of creating a duplicate.