Skip to main content

Error Handling

This guide covers error handling strategies, common error types, and best practices for building resilient applications with the Blue Oyster API.

Error Response Format

All API errors follow this standard format:
{
  "error": "Human-readable error message",
  "stack": "Stack trace (development only)",
  "code": "MACHINE_READABLE_ERROR_CODE",
  "details": {
    "additional": "contextual information"
  }
}

HTTP Status Codes

CodeDescriptionRetry
400Bad Request - Invalid parametersNo
401Unauthorized - Authentication requiredNo
403Forbidden - Insufficient permissionsNo
404Not Found - Endpoint or resource doesn’t existNo
429Too Many Requests - Rate limitedYes (with backoff)
500Internal Server Error - Server-side issueYes
502Bad Gateway - Upstream service errorYes
503Service Unavailable - Temporary outageYes
504Gateway Timeout - Request timeoutYes

Common Error Codes

API Errors

TOOL_EXECUTION_FAILED
string
Tool execution encountered an error. Check tool parameters and external service status.
MEMORY_NOT_INITIALIZED
string
MongoDB memory system not configured. Thread persistence unavailable.
INVALID_REQUEST
string
Request body malformed or missing required parameters.
RATE_LIMITED
string
Too many requests. Implement exponential backoff.
MODEL_ERROR
string
AI model processing failed. May be temporary.

Speech-to-Text Errors

AUDIO_PROCESSING_ERROR
string
Audio file processing failed. Check file format and size.
INVALID_AUDIO_FORMAT
string
Audio file format not supported.
AUDIO_TOO_LARGE
string
Audio file exceeds size limit (25MB).

Error Handling Strategies

Basic Error Handling

async function makeAPIRequest(endpoint, data) {
  try {
    const response = await fetch(`${BASE_URL}${endpoint}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`API Error: ${errorData.error}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

Exponential Backoff

class RetryHandler {
  constructor(maxRetries = 3, baseDelay = 1000) {
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
  }

  async executeWithRetry(operation) {
    let lastError;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;

        if (!this.shouldRetry(error) || attempt === this.maxRetries) {
          throw error;
        }

        const delay = this.calculateDelay(attempt);
        await this.sleep(delay);
      }
    }

    throw lastError;
  }

  shouldRetry(error) {
    // Retry on network errors and server errors
    if (error.name === 'TypeError' || error.message.includes('fetch')) {
      return true; // Network error
    }

    // Retry on specific HTTP status codes
    const retryableStatuses = [429, 500, 502, 503, 504];
    return retryableStatuses.some(status =>
      error.message.includes(status.toString())
    );
  }

  calculateDelay(attempt) {
    // Exponential backoff with jitter
    const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
    const jitter = Math.random() * 0.1 * exponentialDelay;
    return exponentialDelay + jitter;
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const retryHandler = new RetryHandler();

const result = await retryHandler.executeWithRetry(async () => {
  return await chat.sendMessage("Hello!", "friend");
});

Circuit Breaker Pattern

class CircuitBreaker {
  constructor(failureThreshold = 5, recoveryTimeout = 60000) {
    this.failureThreshold = failureThreshold;
    this.recoveryTimeout = recoveryTimeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }

  async execute(operation) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }

  getState() {
    return this.state;
  }
}

// Usage
const circuitBreaker = new CircuitBreaker();

const result = await circuitBreaker.execute(async () => {
  return await chat.sendMessage("Hello!", "friend");
});

Comprehensive Error Handler

class APIErrorHandler {
  constructor(chat) {
    this.chat = chat;
    this.retryHandler = new RetryHandler();
    this.circuitBreaker = new CircuitBreaker();
  }

  async sendMessageWithErrorHandling(message, personality = 'friend') {
    try {
      return await this.circuitBreaker.execute(async () => {
        return await this.retryHandler.executeWithRetry(async () => {
          return await this.chat.sendMessage(message, personality);
        });
      });
    } catch (error) {
      return await this.handleError(error, message, personality);
    }
  }

  async handleError(error, originalMessage, personality) {
    const errorType = this.categorizeError(error);

    switch (errorType) {
      case 'network':
        return await this.handleNetworkError(originalMessage, personality);

      case 'rate_limit':
        return await this.handleRateLimit(originalMessage, personality);

      case 'server_error':
        return await this.handleServerError(originalMessage, personality);

      case 'validation':
        return await this.handleValidationError(error, originalMessage);

      default:
        return await this.handleUnknownError(error, originalMessage);
    }
  }

  categorizeError(error) {
    const message = error.message.toLowerCase();

    if (message.includes('network') || message.includes('fetch')) {
      return 'network';
    }
    if (message.includes('429') || message.includes('rate')) {
      return 'rate_limit';
    }
    if (message.includes('500') || message.includes('502') || message.includes('503')) {
      return 'server_error';
    }
    if (message.includes('400') || message.includes('invalid')) {
      return 'validation';
    }
    return 'unknown';
  }

  async handleNetworkError(message, personality) {
    // Try with simplified message
    const simplifiedMessage = message.length > 100 ?
      message.substring(0, 100) + '...' : message;

    try {
      return await this.chat.sendMessage(simplifiedMessage, personality);
    } catch (fallbackError) {
      throw new Error('Network connectivity issues. Please check your connection and try again.');
    }
  }

  async handleRateLimit(message, personality) {
    // Wait and retry once
    await this.sleep(2000);
    try {
      return await this.chat.sendMessage(message, personality);
    } catch (retryError) {
      throw new Error('Rate limit exceeded. Please wait a moment before trying again.');
    }
  }

  async handleServerError(message, personality) {
    // Try with different personality or simplified request
    const fallbackPersonality = personality === 'guru' ? 'friend' : 'guru';

    try {
      return await this.chat.sendMessage(message, fallbackPersonality);
    } catch (fallbackError) {
      throw new Error('Service temporarily unavailable. Please try again in a few minutes.');
    }
  }

  async handleValidationError(error, message) {
    // Extract validation details and provide helpful feedback
    const details = error.details || {};
    let helpfulMessage = 'Request validation failed. ';

    if (details.missingField) {
      helpfulMessage += `Missing required field: ${details.missingField}`;
    } else if (details.invalidField) {
      helpfulMessage += `Invalid field: ${details.invalidField}`;
    } else {
      helpfulMessage += 'Please check your request parameters.';
    }

    throw new Error(helpfulMessage);
  }

  async handleUnknownError(error, message) {
    // Log for debugging and provide generic message
    console.error('Unknown API error:', error);
    throw new Error('An unexpected error occurred. Please try again or contact support if the issue persists.');
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const errorHandler = new APIErrorHandler(chat);

const result = await errorHandler.sendMessageWithErrorHandling(
  "Tell me about meditation",
  "friend"
);

Error Monitoring

Error Tracking

class ErrorTracker {
  constructor() {
    this.errors = [];
    this.maxErrors = 100;
  }

  trackError(error, context = {}) {
    const errorEntry = {
      timestamp: new Date().toISOString(),
      error: error.message,
      stack: error.stack,
      context,
      userAgent: navigator.userAgent,
      url: window.location.href
    };

    this.errors.push(errorEntry);

    // Keep only recent errors
    if (this.errors.length > this.maxErrors) {
      this.errors.shift();
    }

    // Send to error reporting service
    this.reportError(errorEntry);
  }

  reportError(errorEntry) {
    // Send to your error reporting service
    console.error('API Error:', errorEntry);

    // Example: Send to analytics service
    if (window.gtag) {
      window.gtag('event', 'exception', {
        description: errorEntry.error,
        fatal: false
      });
    }
  }

  getErrorSummary() {
    const summary = {
      totalErrors: this.errors.length,
      recentErrors: this.errors.slice(-10),
      errorTypes: this.categorizeErrors()
    };

    return summary;
  }

  categorizeErrors() {
    const categories = {};

    this.errors.forEach(error => {
      const category = this.categorizeError(error.error);
      categories[category] = (categories[category] || 0) + 1;
    });

    return categories;
  }

  categorizeError(errorMessage) {
    const message = errorMessage.toLowerCase();

    if (message.includes('network') || message.includes('fetch')) {
      return 'Network';
    }
    if (message.includes('rate') || message.includes('429')) {
      return 'Rate Limit';
    }
    if (message.includes('500') || message.includes('server')) {
      return 'Server Error';
    }
    if (message.includes('400') || message.includes('invalid')) {
      return 'Validation';
    }
    return 'Other';
  }
}

// Usage
const errorTracker = new ErrorTracker();

// Wrap API calls with error tracking
try {
  const result = await chat.sendMessage("Hello!", "friend");
} catch (error) {
  errorTracker.trackError(error, {
    action: 'send_message',
    personality: 'friend',
    messageLength: 6
  });

  // Handle error...
}

Best Practices

Always implement error boundaries in your UI to prevent crashes.
Provide user-friendly error messages instead of raw API errors.
Implement graceful degradation when services are unavailable.
Log errors for debugging but don’t expose sensitive information.
Use exponential backoff for retry logic to avoid overwhelming services.
Monitor error rates and implement circuit breakers for resilience.