Error Handling
Comprehensive guide to API error codes and handling.
All API errors follow a consistent format. Understanding error codes helps you build robust integrations.
Error Response Format
Every error response has this structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": { ... }
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}| Field | Description |
|---|---|
success | Always false for errors |
error.code | Machine-readable error code (use this for logic) |
error.message | Human-readable description (safe to display to users) |
error.details | Additional context (optional, varies by error) |
request_id | Unique request ID for debugging/support |
Error Code Reference
Authentication Errors (4xx)
Billing Errors (4xx)
Validation Errors (400)
Rate Limiting Errors (429)
Deprecated Endpoints (410)
Server Errors (5xx)
Error Handling Examples
Basic Error Handling
async function getBenchmark(origin, dest) {
const response = await fetch('https://cargobloom.io/api/v1/benchmark/basic', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CARGOBLOOM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ origin_country: origin, dest_country: dest }),
});
const result = await response.json();
if (!result.success) {
switch (result.error.code) {
case 'RATE_LIMIT_EXCEEDED':
// Wait and retry
const retryAfter = response.headers.get('Retry-After') || 1;
await new Promise(r => setTimeout(r, retryAfter * 1000));
return getBenchmark(origin, dest);
case 'CREDIT_LIMIT_EXCEEDED':
throw new Error('Monthly credit limit reached. Please upgrade your plan.');
case 'VALIDATION_ERROR':
throw new Error(`Invalid request: ${result.error.message}`);
case 'INTERNAL_ERROR':
case 'SERVICE_UNAVAILABLE':
throw new Error('Service temporarily unavailable. Please try again.');
default:
throw new Error(result.error.message);
}
}
return result.data;
}import requests
import time
def get_benchmark(origin: str, dest: str) -> dict:
response = requests.post(
'https://cargobloom.io/api/v1/benchmark/basic',
headers={
'Authorization': f'Bearer {os.environ["CARGOBLOOM_API_KEY"]}',
'Content-Type': 'application/json',
},
json={'origin_country': origin, 'dest_country': dest}
)
result = response.json()
if not result['success']:
error_code = result['error']['code']
error_message = result['error']['message']
if error_code == 'RATE_LIMIT_EXCEEDED':
retry_after = int(response.headers.get('Retry-After', 1))
time.sleep(retry_after)
return get_benchmark(origin, dest)
elif error_code == 'CREDIT_LIMIT_EXCEEDED':
raise Exception('Monthly credit limit reached. Please upgrade your plan.')
elif error_code == 'VALIDATION_ERROR':
raise ValueError(f'Invalid request: {error_message}')
elif error_code in ('INTERNAL_ERROR', 'SERVICE_UNAVAILABLE'):
raise Exception('Service temporarily unavailable. Please try again.')
else:
raise Exception(error_message)
return result['data']Retryable vs Non-Retryable Errors
| Error Code | Retryable? | Action |
|---|---|---|
MISSING_API_KEY | No | Fix code |
INVALID_API_KEY | No | Check/regenerate key |
INSUFFICIENT_PERMISSIONS | No | Update key permissions |
CREDIT_LIMIT_EXCEEDED | No* | Enable overage or wait |
VALIDATION_ERROR | No | Fix request parameters |
RATE_LIMIT_EXCEEDED | Yes | Wait and retry |
INTERNAL_ERROR | Yes | Retry with backoff |
SERVICE_UNAVAILABLE | Yes | Retry with backoff |
*Technically retryable after credits reset or overage enabled.
Logging Errors
Include the request_id when logging errors for easier debugging:
if (!result.success) {
console.error('API Error:', {
code: result.error.code,
message: result.error.message,
requestId: result.request_id,
timestamp: new Date().toISOString(),
});
}Getting Support
When contacting support about an error:
- Include the
request_idfrom the error response - Describe what you were trying to do
- Include the full error response (redact your API key)
- Note the approximate time of the error
Status Page
Check status.cargobloom.io for known issues before contacting support.
Last updated: February 1, 2026