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#

Typescript (27 lines)
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#

Python (19 lines)
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#

Typescript (44 lines)
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#

Typescript (40 lines)
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(),
  });
}