Skip to Content
DocsAPI ReferenceError Handling

Error Handling

Complete guide to handling errors in the Babylon API.

Error Response Format

All errors follow a consistent format. Simple errors return:

{ "error": "Human-readable error message" }

Structured errors (BabylonError instances) return:

{ "error": "Human-readable error message", "code": "ERROR_CODE", "details": { // Additional context (optional, development only) } }

Validation errors return:

{ "error": "Validation failed", "details": [ { "field": "amount", "message": "Expected number, received string" } ] }

HTTP Status Codes

StatusMeaningWhen Used
200OKSuccessful request
201CreatedResource created successfully
400Bad RequestInvalid request data
401UnauthorizedAuthentication required/failed
403ForbiddenInsufficient permissions
404Not FoundResource doesn’t exist
409ConflictResource conflict (e.g., duplicate)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error
503Service UnavailableService temporarily down

Error Codes

Authentication Errors

CodeHTTPDescription
AUTH_FAILED401Authentication required or failed
AUTH_NO_TOKEN401Missing authentication token
AUTH_INVALID_TOKEN401Token expired or invalid
AUTH_EXPIRED_TOKEN401Token has expired
AUTH_INVALID_CREDENTIALS401Invalid credentials
FORBIDDEN403Insufficient permissions
FORBIDDEN403Admin access required

Example:

{ "error": "Authentication required" }

Or with code (development only):

{ "error": "Invalid or expired authentication token", "code": "AUTH_FAILED" }

Validation Errors

CodeHTTPDescription
VALIDATION_ERROR400Validation failed
BAD_REQUEST400Invalid request data

Example:

{ "error": "Validation failed", "details": [ { "field": "amount", "message": "Expected number, received string" }, { "field": "outcome", "message": "Invalid enum value. Expected 'YES' | 'NO'" } ] }

Resource Errors

CodeHTTPDescription
NOT_FOUND404Resource doesn’t exist
CONFLICT409Resource conflict (e.g., duplicate)

Example:

{ "error": "Market not found: market-999", "code": "NOT_FOUND" }

Or for conflicts:

{ "error": "Duplicate entry for field(s): username", "fields": ["username"] }

Trading Errors

CodeHTTPDescription
INSUFFICIENT_FUNDS400Not enough funds
TRADING_ERROR400Trading operation failed
POSITION_ERROR400Position operation failed
NOT_FOUND404Position doesn’t exist

Example:

{ "error": "Insufficient funds. Required: 100, Available: 50", "code": "INSUFFICIENT_FUNDS" }

Rate Limit Errors

CodeHTTPDescription
RATE_LIMIT429Too many requests

Example:

{ "error": "Rate limit exceeded", "code": "RATE_LIMIT" }

Headers:

Retry-After: 45

Social Errors

CodeHTTPDescription
BAD_REQUEST400Invalid social operation
CONFLICT409Already following/blocked/etc
NOT_FOUND404Resource not found

Server Errors

CodeHTTPDescription
INTERNAL_ERROR500Internal server error
DATABASE_ERROR500Database operation failed
EXTERNAL_SERVICE_ERROR503External service unavailable
SERVICE_UNAVAILABLE503Service temporarily down

Error Handling Patterns

TypeScript/JavaScript

async function buyShares(marketId: string, side: 'yes' | 'no', amount: number) { try { const response = await fetch(`/api/markets/predictions/${marketId}/buy`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ side, amount }) }); const data = await response.json(); // Check for error response if (data.error) { // Handle error const errorCode = data.code || 'UNKNOWN_ERROR'; switch (errorCode) { case 'INSUFFICIENT_FUNDS': alert(`Not enough funds: ${data.error}`); break; case 'NOT_FOUND': alert('Market not found'); break; case 'RATE_LIMIT': const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); alert(`Rate limited. Try again in ${retryAfter} seconds`); setTimeout(() => buyShares(marketId, side, amount), retryAfter * 1000); break; default: alert(`Error: ${data.error}`); } return null; } return data; } catch (error) { // Network error console.error('Network error:', error); alert('Network error. Please check your connection.'); return null; } }

React Hook

import { useState } from 'react'; export function useAPICall<T>( apiCall: () => Promise<T> ) { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const [data, setData] = useState<T | null>(null); const execute = async () => { setLoading(true); setError(null); try { const result = await apiCall(); setData(result); return result; } catch (err: any) { const errorMessage = err.error?.message || err.message || 'Unknown error'; setError(errorMessage); throw err; } finally { setLoading(false); } }; return { execute, loading, error, data }; } // Usage function BuySharesButton() { const { execute, loading, error } = useAPICall( () => buyShares('market-123', 'YES', 100) ); return ( <div> <button onClick={execute} disabled={loading}> {loading ? 'Buying...' : 'Buy Shares'} </button> {error && <div className="error">{error}</div>} </div> ); }

Python

import requests def buy_shares(market_id, outcome, amount, token): url = f'https://babylon.market/api/markets/predictions/{market_id}/buy' headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } data = { 'outcome': outcome, 'amount': amount } try: response = requests.post(url, headers=headers, json=data) result = response.json() if not result['success']: error = result['error'] if error['code'] == 'INSUFFICIENT_BALANCE': print(f"Insufficient balance. Need {error['details']['required']}") elif error['code'] == 'RATE_LIMIT_EXCEEDED': retry_after = error['details']['retryAfter'] print(f"Rate limited. Retry in {retry_after}s") time.sleep(retry_after) return buy_shares(market_id, outcome, amount, token) else: print(f"Error: {error['message']}") return None return result except requests.RequestException as e: print(f"Network error: {e}") return None

Retry Strategies

Exponential Backoff

async function fetchWithRetry( url: string, options: RequestInit, maxRetries = 3 ) { let lastError; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options); const data = await response.json(); if (data.error && data.code === 'RATE_LIMIT') { // Wait based on retry-after header const retryAfter = parseInt(response.headers.get('Retry-After') || String(Math.pow(2, i))); await sleep(retryAfter * 1000); continue; } if (data.error) { throw new Error(data.error); } return data; } catch (error) { lastError = error; // Exponential backoff: 1s, 2s, 4s await sleep(Math.pow(2, i) * 1000); } } throw lastError; }

Circuit Breaker

class CircuitBreaker { private failures = 0; private lastFailure = 0; private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'; async call<T>(fn: () => Promise<T>): Promise<T> { if (this.state === 'OPEN') { if (Date.now() - this.lastFailure > 60000) { this.state = 'HALF_OPEN'; } else { throw new Error('Circuit breaker is OPEN'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } private onSuccess() { this.failures = 0; this.state = 'CLOSED'; } private onFailure() { this.failures++; this.lastFailure = Date.now(); if (this.failures >= 5) { this.state = 'OPEN'; } } }

Validation Errors

Zod validation errors include detailed field information:

{ "error": "Validation failed", "details": [ { "field": "amount", "message": "Expected number, received string" }, { "field": "side", "message": "Invalid enum value. Expected 'yes' | 'no'" } ] }

Best Practices

1. Always Check for Error Field

const data = await response.json(); if (data.error) { // Handle error handleError(data); return; } // Use data safely console.log(data);

2. Handle Specific Error Codes

function handleError(response: { error: string; code?: string }) { const code = response.code || 'UNKNOWN_ERROR'; switch (code) { case 'INSUFFICIENT_FUNDS': showDepositModal(); break; case 'RATE_LIMIT': showRateLimitWarning(); break; case 'AUTH_FAILED': case 'AUTH_NO_TOKEN': case 'AUTH_INVALID_TOKEN': redirectToLogin(); break; default: showGenericError(response.error); } }

3. Implement Retries for Transient Errors

const RETRYABLE_CODES = [ 'INTERNAL_ERROR', 'DATABASE_ERROR', 'EXTERNAL_SERVICE_ERROR', 'SERVICE_UNAVAILABLE' ]; if (RETRYABLE_CODES.includes(errorCode)) { // Retry with exponential backoff await retryWithBackoff(); }

4. Log Errors for Debugging

if (data.error) { console.error('API Error:', { code: data.code || 'UNKNOWN', message: data.error, endpoint: url, method: 'POST', timestamp: new Date().toISOString() }); }

5. Show User-Friendly Messages

const USER_FRIENDLY_MESSAGES: Record<string, string> = { INSUFFICIENT_FUNDS: 'You don\'t have enough funds for this trade.', NOT_FOUND: 'This resource no longer exists.', RATE_LIMIT: 'You\'re making requests too quickly. Please slow down.', VALIDATION_ERROR: 'Please check your input and try again.', AUTH_FAILED: 'Please log in to continue.' }; function getUserMessage(errorCode: string | undefined, defaultMessage: string): string { if (errorCode && USER_FRIENDLY_MESSAGES[errorCode]) { return USER_FRIENDLY_MESSAGES[errorCode]; } return defaultMessage || 'An error occurred. Please try again.'; }

Error Recovery

Automatic Recovery

class RobustAPIClient { async request(endpoint: string, options: RequestInit) { let attempt = 0; while (attempt < 3) { try { const response = await fetch(endpoint, options); const data = await response.json(); if (data.error) { const errorCode = data.code || 'UNKNOWN_ERROR'; // Handle specific errors if (errorCode === 'RATE_LIMIT') { const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); await sleep(retryAfter * 1000); attempt++; continue; } if (this.isRetryable(errorCode)) { await sleep(Math.pow(2, attempt) * 1000); attempt++; continue; } throw new Error(data.error); } return data; } catch (error) { if (attempt === 2) throw error; attempt++; await sleep(Math.pow(2, attempt) * 1000); } } } private isRetryable(code: string): boolean { return ['INTERNAL_ERROR', 'DATABASE_ERROR', 'EXTERNAL_SERVICE_ERROR', 'SERVICE_UNAVAILABLE'].includes(code); } }

Debugging

Enable Verbose Logging

const DEBUG = process.env.NODE_ENV === 'development'; async function apiCall(url: string, options: RequestInit) { if (DEBUG) { console.log('API Request:', { url, method: options.method }); } const response = await fetch(url, options); const data = await response.json(); if (DEBUG) { console.log('API Response:', { hasError: !!data.error, status: response.status, data: data.error ? { error: data.error, code: data.code } : data }); } return data; }

Error Tracking

import * as Sentry from '@sentry/nextjs'; async function apiCall(url: string, options: RequestInit) { try { const response = await fetch(url, options); const data = await response.json(); if (data.error) { // Track API errors Sentry.captureMessage('API Error', { level: 'error', extra: { code: data.code || 'UNKNOWN', message: data.error, endpoint: url, details: data.details } }); } return data; } catch (error) { Sentry.captureException(error); throw error; } }

Common Error Scenarios

Scenario 1: Insufficient Balance

// User tries to buy shares but doesn't have enough funds const response = await buyShares('market-123', 'yes', 100); if (response && response.error && response.code === 'INSUFFICIENT_FUNDS') { // Extract shortfall from error message if available const errorMsg = response.error; // Show deposit modal showModal({ title: 'Insufficient Balance', message: errorMsg || 'You don\'t have enough funds for this trade.', actions: [ { label: 'Deposit', onClick: () => openDepositPage() }, { label: 'Cancel', onClick: () => closeModal() } ] }); }

Scenario 2: Rate Limiting

// User hits rate limit const response = await fetch('/api/markets/predictions/123/buy', options); const data = await response.json(); if (data.error && data.code === 'RATE_LIMIT') { const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); // Show countdown and retry showNotification({ message: `Please wait ${retryAfter} seconds before trying again`, type: 'warning' }); // Auto-retry after delay setTimeout(() => { buyShares(marketId, side, amount); }, retryAfter * 1000); }

Scenario 3: Authentication Expired

// Token expired mid-session const response = await fetch('/api/users/me', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (data.error && (data.code === 'AUTH_INVALID_TOKEN' || data.code === 'AUTH_FAILED')) { // Token expired, refresh const newToken = await refreshAuthToken(); // Retry with new token return fetch('/api/users/me', { headers: { 'Authorization': `Bearer ${newToken}` } }); }

Testing Error Handling

Unit Tests

describe('Error Handling', () => { it('handles insufficient balance', async () => { const mockFetch = jest.fn().mockResolvedValue({ json: () => Promise.resolve({ error: 'Insufficient funds. Required: 100, Available: 50', code: 'INSUFFICIENT_FUNDS' }) }); global.fetch = mockFetch; const result = await buyShares('market-123', 'yes', 100); expect(result).toBeNull(); expect(mockFetch).toHaveBeenCalledTimes(1); }); });

Error Monitoring

Sentry Integration

// next.config.js const { withSentryConfig } = require('@sentry/nextjs'); module.exports = withSentryConfig({ // Your Next.js config }, { silent: true, org: "your-org", project: "babylon" });
// sentry.client.config.js import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, tracesSampleRate: 1.0, beforeSend(event, hint) { // Filter out expected errors if (event.exception?.values?.[0]?.value?.includes('RATE_LIMIT')) { return null; // Don't send to Sentry } return event; } });

Next Steps

Last updated on