Error Responses

Understanding and handling API error responses.

Error Format

All errors follow a consistent format:

{
  "success": false,
  "error": "Human-readable description",
  "code": "BAD_REQUEST",
  "requestId": "req_abc123xyz",
  "details": [{ "field": "...", "message": "..." }]
}
FieldDescription
successAlways false for error responses
errorHuman-readable error description
codeError code string (e.g. UNAUTHORIZED, RATE_LIMITED)
requestIdUnique request ID for debugging (optional)
detailsField-level validation errors (optional)
retryAfterPresent on some 429 responses (body field; optional)

Some handlers also include route-specific fields such as hint, required, status, reason, or message.

HTTP Status Codes

2xx Success

CodeMeaning
200Success
201Created

4xx Client Errors

CodeMeaning
400Bad Request
401Unauthorized
403Forbidden
404Not Found
413Payload Too Large
429Too Many Requests

5xx Server Errors

CodeMeaning
500Internal Server Error
503Service Unavailable
504Gateway Timeout

Common Errors

400 Bad Request

Invalid request parameters.

{
  "success": false,
  "error": "Invalid market address format",
  "code": "VALIDATION_ERROR",
  "requestId": "req_abc123xyz",
  "details": [{ "field": "address", "message": "Must be a valid base58 public key" }]
}

Common causes:

  • Invalid public key format
  • Missing required parameters
  • Invalid parameter values

401 Unauthorized

Authentication failed.

{
  "success": false,
  "error": "Invalid API key",
  "code": "UNAUTHORIZED",
  "requestId": "req_abc123xyz"
}

Common causes:

  • Missing API key
  • Invalid API key
  • Expired API key

403 Forbidden

Authenticated but not allowed.

{
  "success": false,
  "error": "API key does not have access to this resource",
  "code": "FORBIDDEN",
  "requestId": "req_abc123xyz"
}

Common causes:

  • Insufficient permissions
  • Accessing another user's data
  • Feature not available for tier

404 Not Found

Resource doesn't exist.

{
  "success": false,
  "error": "Market not found",
  "code": "NOT_FOUND",
  "requestId": "req_abc123xyz"
}

Common causes:

  • Invalid address
  • Resource deleted
  • Wrong environment (mainnet vs devnet)

413 Payload Too Large

Request body exceeds configured API limits.

{
  "success": false,
  "error": "Request body too large",
  "code": "BAD_REQUEST",
  "requestId": "req_abc123xyz"
}

429 Too Many Requests

Rate limit exceeded (global IP limit or API key limit).

{
  "success": false,
  "error": "API key rate limit exceeded",
  "code": "RATE_LIMITED",
  "requestId": "req_abc123xyz",
  "retryAfter": "42s"
}

Headers:

  • Retry-After
  • X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset
  • X-RateLimit-Limit-Key / X-RateLimit-Remaining-Key / X-RateLimit-Reset-Key

500 Internal Server Error

Server-side error.

{
  "success": false,
  "error": "Internal server error",
  "code": "INTERNAL_ERROR",
  "requestId": "req_abc123xyz"
}

Action: Contact support with the requestId.

503 Service Unavailable

Temporary service issue.

{
  "success": false,
  "error": "Database temporarily unavailable",
  "code": "SERVICE_UNAVAILABLE",
  "requestId": "req_abc123xyz"
}

Error Handling

JavaScript/TypeScript

async function fetchMarket(address: string) {
  try {
    const response = await fetch(`/api/markets/${address}`);

    if (!response.ok) {
      const error = await response.json();

      switch (error.code) {
        case 'UNAUTHORIZED':
          throw new Error('Please check your API key');
        case 'NOT_FOUND':
          throw new Error('Market not found');
        case 'RATE_LIMITED':
          const retryAfter = error.retryAfter || 60;
          throw new Error(`Rate limited. Retry after ${retryAfter}s`);
        default:
          throw new Error(error.error || 'Unknown error');
      }
    }

    return response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

Python

import requests

def fetch_market(address):
    response = requests.get(f'{BASE_URL}/api/markets/{address}')

    if response.status_code == 200:
        return response.json()

    error = response.json()

    if response.status_code == 401:
        raise AuthenticationError(error['error'])
    elif response.status_code == 404:
        raise NotFoundError(f"Market {address} not found")
    elif response.status_code == 429:
        raise RateLimitError(error.get('retryAfter', 60))
    else:
        raise APIError(error['error'])

React

function useMarket(address: string) {
  const [data, setData] = useState(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(`/api/markets/${address}`);

        if (!response.ok) {
          const errorData = await response.json();
          setError(getErrorMessage(errorData));
          return;
        }

        setData(await response.json());
        setError(null);
      } catch (e) {
        setError('Network error. Please try again.');
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [address]);

  return { data, error, loading };
}

function getErrorMessage(error: any): string {
  switch (error.code) {
    case 'NOT_FOUND':
      return 'Market not found. Please check the address.';
    case 'RATE_LIMITED':
      return 'Too many requests. Please wait a moment.';
    case 'INTERNAL_ERROR':
      return 'Server error. Please try again later.';
    default:
      return error.error || 'An error occurred.';
  }
}

Validation Errors

Detailed validation error format:

{
  "success": false,
  "error": "Validation error",
  "code": "VALIDATION_ERROR",
  "requestId": "req_abc123xyz",
  "details": [
    { "field": "limit", "message": "Must be between 1 and 100" },
    { "field": "status", "message": "Must be one of: active, matured, all" }
  ]
}

Retry Logic

When to Retry

HTTP StatusRetry?Strategy
400NoFix request
401NoCheck credentials
403NoCheck permissions
404NoCheck resource
413NoReduce payload size
429YesHonor Retry-After
500YesExponential backoff + jitter
503YesExponential backoff + jitter
504YesExponential backoff + jitter

Retry Implementation

async function fetchWithRetry(url: string, maxRetries = 3, initialDelay = 1000) {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url);

      if (response.ok) {
        return response.json();
      }

      const error = await response.json();

      // Retry rate limits using Retry-After when present
      if (response.status === 429) {
        const retryAfterHeader = response.headers.get('retry-after');
        const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : NaN;
        const retryDelay =
          Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
            ? retryAfterSeconds * 1000
            : initialDelay * Math.pow(2, attempt);
        await sleep(retryDelay);
        continue;
      }

      // Don't retry other client errors
      if (response.status >= 400 && response.status < 500) {
        throw new Error(error.error);
      }

      // Server error - use backoff
      await sleep(initialDelay * Math.pow(2, attempt));
    } catch (error) {
      lastError = error as Error;
    }
  }

  throw lastError || new Error('Max retries exceeded');
}

Debugging

Using Request ID

Every response includes a request ID:

X-Request-Id: req_abc123xyz

Include this when contacting support.

Logging

Log errors with full context:

function logApiError(error: ApiError, context: object) {
  console.error('API Error', {
    code: error.code,
    error: error.error,
    requestId: error.requestId,
    ...context,
    timestamp: new Date().toISOString(),
  });
}