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:Copy
{
"error": "Human-readable error message",
"stack": "Stack trace (development only)",
"code": "MACHINE_READABLE_ERROR_CODE",
"details": {
"additional": "contextual information"
}
}
HTTP Status Codes
| Code | Description | Retry |
|---|---|---|
400 | Bad Request - Invalid parameters | No |
401 | Unauthorized - Authentication required | No |
403 | Forbidden - Insufficient permissions | No |
404 | Not Found - Endpoint or resource doesn’t exist | No |
429 | Too Many Requests - Rate limited | Yes (with backoff) |
500 | Internal Server Error - Server-side issue | Yes |
502 | Bad Gateway - Upstream service error | Yes |
503 | Service Unavailable - Temporary outage | Yes |
504 | Gateway Timeout - Request timeout | Yes |
Common Error Codes
API Errors
Tool execution encountered an error. Check tool parameters and external service status.
MongoDB memory system not configured. Thread persistence unavailable.
Request body malformed or missing required parameters.
Too many requests. Implement exponential backoff.
AI model processing failed. May be temporary.
Speech-to-Text Errors
Audio file processing failed. Check file format and size.
Audio file format not supported.
Audio file exceeds size limit (25MB).
Error Handling Strategies
Basic Error Handling
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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.
