Comprehensive guide to handling API errors in Bootspring.
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 }
Status Description 400 Bad Request - Invalid request body or parameters 401 Unauthorized - Missing or invalid authentication 403 Forbidden - Insufficient permissions 404 Not Found - Resource doesn't exist 409 Conflict - Resource already exists 422 Unprocessable Entity - Validation failed 429 Too Many Requests - Rate limit exceeded 500 Internal Server Error - Server-side error 502 Bad Gateway - Upstream service error 503 Service Unavailable - Temporarily unavailable
Code Message 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
Code Message 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
Code Message 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
Code Message not_foundThe requested resource was not found already_existsA resource with this identifier already exists deletedThe resource has been deleted
Code Message rate_limitedToo many requests, please slow down concurrent_limitToo many concurrent requests
Code Message internal_errorAn internal server error occurred service_unavailableThe service is temporarily unavailable upstream_errorAn upstream service returned an error timeoutThe request timed out
1 class 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
21 async 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
33 try {
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 }
1 import requests
2 from typing import Optional , List , Dict
3
4 class 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
16 def 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
25 try :
26 projects = api_request (
27 'https://api.bootspring.dev/v1/projects' ,
28 headers = { 'Authorization' : f'Bearer { token } ' }
29 )
30 except 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 } " )
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 }
Code Description 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
1 async 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 }
1 class 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 }
Include the request_id when reporting issues:
1 try {
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 }
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"
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 }