Error Handling

Comprehensive guide to handling API errors in Bootspring.

Error Response Format#

All errors follow a consistent JSON format:

1{ 2 "error": { 3 "code": "error_code", 4 "message": "Human-readable error message", 5 "details": [ 6 { 7 "field": "email", 8 "message": "Invalid email format" 9 } 10 ], 11 "request_id": "req_abc123" 12 } 13}

HTTP Status Codes#

StatusDescription
400Bad Request - Invalid request body or parameters
401Unauthorized - Missing or invalid authentication
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
409Conflict - Resource already exists
422Unprocessable Entity - Validation failed
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Server-side error
502Bad Gateway - Upstream service error
503Service Unavailable - Temporarily unavailable

Error Codes Reference#

Authentication Errors (401)#

CodeMessage
unauthorizedMissing or invalid authentication token
token_expiredThe authentication token has expired
token_revokedThe authentication token has been revoked
invalid_api_keyThe API key is invalid or inactive
api_key_expiredThe API key has expired

Permission Errors (403)#

CodeMessage
forbiddenYou don't have permission to access this resource
insufficient_scopeToken lacks required scope
tier_requiredThis feature requires a higher tier
quota_exceededYou've exceeded your plan's quota

Validation Errors (400, 422)#

CodeMessage
invalid_requestThe request body is invalid
validation_failedOne or more fields failed validation
missing_fieldA required field is missing
invalid_fieldA field has an invalid value
invalid_jsonThe request body is not valid JSON

Resource Errors (404, 409)#

CodeMessage
not_foundThe requested resource was not found
already_existsA resource with this identifier already exists
deletedThe resource has been deleted

Rate Limiting Errors (429)#

CodeMessage
rate_limitedToo many requests, please slow down
concurrent_limitToo many concurrent requests

Server Errors (5xx)#

CodeMessage
internal_errorAn internal server error occurred
service_unavailableThe service is temporarily unavailable
upstream_errorAn upstream service returned an error
timeoutThe request timed out

Handling Errors#

JavaScript/TypeScript#

1class BootspringError extends Error { 2 code: string; 3 status: number; 4 details?: Array<{ field: string; message: string }>; 5 requestId?: string; 6 7 constructor(response: ErrorResponse, status: number) { 8 super(response.error.message); 9 this.name = 'BootspringError'; 10 this.code = response.error.code; 11 this.status = status; 12 this.details = response.error.details; 13 this.requestId = response.error.request_id; 14 } 15 16 isRetryable(): boolean { 17 return [429, 500, 502, 503].includes(this.status); 18 } 19} 20 21async function apiRequest(url: string, options: RequestInit) { 22 const response = await fetch(url, options); 23 24 if (!response.ok) { 25 const data = await response.json(); 26 throw new BootspringError(data, response.status); 27 } 28 29 return response.json(); 30} 31 32// Usage 33try { 34 const projects = await apiRequest('/api/projects', { 35 headers: { Authorization: `Bearer ${token}` }, 36 }); 37} catch (error) { 38 if (error instanceof BootspringError) { 39 switch (error.code) { 40 case 'unauthorized': 41 // Redirect to login 42 break; 43 case 'token_expired': 44 // Refresh token and retry 45 break; 46 case 'rate_limited': 47 // Wait and retry 48 break; 49 case 'validation_failed': 50 // Show field errors 51 error.details?.forEach((d) => { 52 console.log(`${d.field}: ${d.message}`); 53 }); 54 break; 55 default: 56 // Show generic error 57 console.error(error.message); 58 } 59 } 60}

Python#

1import requests 2from typing import Optional, List, Dict 3 4class BootspringError(Exception): 5 def __init__(self, response: dict, status_code: int): 6 error = response.get('error', {}) 7 super().__init__(error.get('message', 'Unknown error')) 8 self.code = error.get('code') 9 self.status = status_code 10 self.details = error.get('details', []) 11 self.request_id = error.get('request_id') 12 13 def is_retryable(self) -> bool: 14 return self.status in [429, 500, 502, 503] 15 16def api_request(url: str, method: str = 'GET', **kwargs): 17 response = requests.request(method, url, **kwargs) 18 19 if not response.ok: 20 raise BootspringError(response.json(), response.status_code) 21 22 return response.json() 23 24# Usage 25try: 26 projects = api_request( 27 'https://api.bootspring.dev/v1/projects', 28 headers={'Authorization': f'Bearer {token}'} 29 ) 30except BootspringError as e: 31 if e.code == 'unauthorized': 32 # Handle auth error 33 pass 34 elif e.code == 'rate_limited': 35 # Wait and retry 36 pass 37 else: 38 print(f"Error: {e.message}")

Validation Error Details#

When validation fails, the details array provides specific information:

1{ 2 "error": { 3 "code": "validation_failed", 4 "message": "Validation failed", 5 "details": [ 6 { 7 "field": "email", 8 "message": "Must be a valid email address", 9 "code": "invalid_email" 10 }, 11 { 12 "field": "name", 13 "message": "Name must be between 2 and 100 characters", 14 "code": "string_length", 15 "min": 2, 16 "max": 100 17 }, 18 { 19 "field": "tags", 20 "message": "Maximum 10 tags allowed", 21 "code": "array_max_length", 22 "max": 10 23 } 24 ] 25 } 26}

Field Validation Codes#

CodeDescription
requiredField is required
invalid_typeField has wrong type
invalid_emailNot a valid email
invalid_urlNot a valid URL
string_lengthString length out of bounds
number_rangeNumber out of range
array_max_lengthArray too long
invalid_enumValue not in allowed list
pattern_mismatchValue doesn't match pattern

Retry Strategies#

Exponential Backoff#

1async function withRetry<T>( 2 fn: () => Promise<T>, 3 maxRetries: number = 3, 4 baseDelay: number = 1000 5): Promise<T> { 6 let lastError: Error; 7 8 for (let attempt = 0; attempt < maxRetries; attempt++) { 9 try { 10 return await fn(); 11 } catch (error) { 12 lastError = error as Error; 13 14 if (error instanceof BootspringError) { 15 // Don't retry non-retryable errors 16 if (!error.isRetryable()) { 17 throw error; 18 } 19 20 // Use Retry-After header if available 21 const retryAfter = error.retryAfter; 22 const delay = retryAfter 23 ? retryAfter * 1000 24 : baseDelay * Math.pow(2, attempt); 25 26 await sleep(delay); 27 } else { 28 throw error; 29 } 30 } 31 } 32 33 throw lastError!; 34}

Circuit Breaker#

1class CircuitBreaker { 2 private failures = 0; 3 private lastFailure: number = 0; 4 private state: 'closed' | 'open' | 'half-open' = 'closed'; 5 6 constructor( 7 private threshold: number = 5, 8 private resetTimeout: number = 60000 9 ) {} 10 11 async execute<T>(fn: () => Promise<T>): Promise<T> { 12 if (this.state === 'open') { 13 if (Date.now() - this.lastFailure > this.resetTimeout) { 14 this.state = 'half-open'; 15 } else { 16 throw new Error('Circuit breaker is open'); 17 } 18 } 19 20 try { 21 const result = await fn(); 22 this.onSuccess(); 23 return result; 24 } catch (error) { 25 this.onFailure(); 26 throw error; 27 } 28 } 29 30 private onSuccess(): void { 31 this.failures = 0; 32 this.state = 'closed'; 33 } 34 35 private onFailure(): void { 36 this.failures++; 37 this.lastFailure = Date.now(); 38 39 if (this.failures >= this.threshold) { 40 this.state = 'open'; 41 } 42 } 43}

Error Logging#

Include the request_id when reporting issues:

1try { 2 await apiRequest('/api/projects'); 3} catch (error) { 4 if (error instanceof BootspringError) { 5 console.error('API Error:', { 6 code: error.code, 7 message: error.message, 8 requestId: error.requestId, 9 status: error.status, 10 }); 11 12 // Report to error tracking service 13 Sentry.captureException(error, { 14 extra: { 15 requestId: error.requestId, 16 code: error.code, 17 }, 18 }); 19 } 20}

Debugging#

Request Headers#

Include these headers for debugging:

curl -X GET https://api.bootspring.dev/v1/projects \ -H "Authorization: Bearer bs_xxx" \ -H "X-Request-Id: my-debug-id" \ -H "X-Debug: true"

Debug Mode Response#

With X-Debug: true, responses include additional information:

1{ 2 "data": { ... }, 3 "debug": { 4 "request_id": "req_abc123", 5 "duration_ms": 45, 6 "region": "us-east-1", 7 "timestamp": "2024-01-15T10:30:00Z" 8 } 9}