Error Handling
This document describes the standard error format, common error codes, and troubleshooting guidance for the Alaikis BI API.
Standard Error Format
All API errors follow a consistent JSON format:
json
{
"detail": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {}
}
}Error Object Fields:
| Field | Type | Description |
|---|---|---|
| code | string | Machine-readable error code |
| message | string | Human-readable description |
| details | object | Additional context (optional) |
For simpler validation errors, FastAPI returns:
json
{
"detail": [
{
"loc": ["body", "sku"],
"msg": "field required",
"type": "value_error.missing"
}
]
}HTTP Status Codes
| Status Code | Category | Description |
|---|---|---|
| 200 | Success | Request completed successfully |
| 201 | Success | Resource created successfully |
| 202 | Success | Request accepted, processing async |
| 400 | Client Error | Bad request - validation failed |
| 401 | Client Error | Unauthorized - authentication required |
| 403 | Client Error | Forbidden - insufficient permissions |
| 404 | Client Error | Not Found - resource doesn't exist |
| 409 | Client Error | Conflict - resource state conflict |
| 422 | Client Error | Unprocessable Entity - validation |
| 429 | Client Error | Too Many Requests - rate limit hit |
| 500 | Server Error | Internal server error |
| 502 | Server Error | Bad Gateway |
| 503 | Server Error | Service Unavailable |
| 504 | Server Error | Gateway Timeout |
Common Error Codes
Authentication Errors
| Error Code | HTTP Status | Description |
|---|---|---|
auth_missing_header | 401 | Authorization header missing |
auth_invalid_token | 401 | Invalid or malformed token |
auth_token_expired | 401 | JWT token has expired |
auth_key_revoked | 401 | API key has been revoked |
auth_invalid_scheme | 401 | Invalid auth scheme (use Bearer) |
auth_user_inactive | 401 | User account is deactivated |
Validation Errors
| Error Code | HTTP Status | Description |
|---|---|---|
validation_failed | 400 | Request validation failed |
missing_required_field | 400 | Required field missing |
invalid_format | 400 | Invalid field format |
value_out_of_range | 400 | Value outside allowed range |
invalid_enum_value | 400 | Value not in allowed enumeration |
Resource Errors
| Error Code | HTTP Status | Description |
|---|---|---|
resource_not_found | 404 | Requested resource not found |
resource_conflict | 409 | Resource state conflict |
resource_already_exists | 409 | Resource already exists |
Rate Limiting
| Error Code | HTTP Status | Description |
|---|---|---|
rate_limit_exceeded | 429 | Too many requests |
concurrency_limit_exceeded | 429 | Too many concurrent requests |
Service Errors
| Error Code | HTTP Status | Description |
|---|---|---|
service_unavailable | 503 | Service temporarily unavailable |
internal_error | 500 | Unexpected server error |
timeout_error | 504 | Request timed out |
Packing Service Errors
| Error Code | HTTP Status | Description |
|---|---|---|
packing_no_items | 400 | No items to pack |
packing_item_too_large | 400 | Item exceeds all pallet dimensions |
packing_no_pallets | 400 | No pallets available |
packing_unsolvable | 400 | Cannot create valid packing |
Product Trace Errors
| Error Code | HTTP Status | Description |
|---|---|---|
trace_no_features | 400 | No features extracted from image |
trace_barcode_not_found | 400 | No barcode detected |
trace_image_too_small | 400 | Image resolution too low |
trace_async_pending | 202 | Processing not complete yet |
Address Service Errors
| Error Code | HTTP Status | Description |
|---|---|---|
address_libpostal_not_installed | 503 | libpostal not available |
address_parse_failed | 400 | Could not parse address |
address_empty_input | 400 | Empty address string |
Handling Errors in Code
Python Example
python
import requests
from requests.exceptions import HTTPError
def call_api(url, json_data, headers):
try:
response = requests.post(
url,
json=json_data,
headers=headers,
timeout=30
)
response.raise_for_status()
return response.json()
except HTTPError as e:
status_code = e.response.status_code
if status_code == 401:
# Handle authentication error
error_data = e.response.json()
raise AuthError(f"Authentication failed: {error_data}")
elif status_code == 429:
# Handle rate limiting - retry with backoff
retry_after = int(e.response.headers.get("Retry-After", 60))
raise RateLimitError(f"Retry after {retry_after}s")
elif status_code == 400:
# Handle validation errors
error_detail = e.response.json().get("detail", {})
raise ValidationError(f"Invalid request: {error_detail}")
elif status_code >= 500:
# Handle server errors - retry logic
raise ServerError(f"Server error: {e}")
else:
raise APIError(f"HTTP {status_code}: {e}")
except requests.exceptions.Timeout:
# Handle timeout
raise TimeoutError("Request timed out")
except requests.exceptions.ConnectionError:
# Handle connection issues
raise ConnectionError("Could not connect to API")
# Usage
try:
result = call_api(
"https://api.alaikis.com/api/packing/calculate",
payload,
headers
)
except RateLimitError as e:
# Implement exponential backoff
time.sleep(retry_after)
except ValidationError as e:
# Fix request parameters
logger.error(f"Validation failed: {e}")JavaScript Example
javascript
class AlaikisAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.alaikis.com';
}
async request(endpoint, options = {}) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
switch (response.status) {
case 401:
throw new AuthenticationError('Invalid API key');
case 404:
throw new NotFoundError('Resource not found');
case 429:
const retryAfter = response.headers.get('Retry-After') || 60;
throw new RateLimitError(`Retry after ${retryAfter}s`, parseInt(retryAfter));
case 500:
throw new ServerError('Internal server error');
default:
throw new APIError(`HTTP ${response.status}: ${errorData.detail || 'Unknown error'}`);
}
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new ConnectionError('Network connection failed');
}
throw error;
}
}
async calculatePacking(payload) {
return this.request('/api/packing/calculate', {
method: 'POST',
body: JSON.stringify(payload)
});
}
}
// Usage with retries
const api = new AlaikisAPI('your-api-key');
async function withRetry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error instanceof RateLimitError) {
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
} else if (error instanceof ServerError && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
} else {
throw error;
}
}
}
}Retry Strategy
Recommended Retry Logic
python
import time
import random
def retry_with_backoff(
func,
max_retries=3,
initial_delay=1.0,
max_delay=30.0,
backoff_factor=2.0,
jitter=True
):
"""
Exponential backoff with jitter retry strategy.
"""
retries = 0
delay = initial_delay
while True:
try:
return func()
except (ServerError, TimeoutError, ConnectionError) as e:
retries += 1
if retries > max_retries:
raise e
# Calculate delay with jitter
if jitter:
delay = min(delay * backoff_factor, max_delay)
sleep_time = delay * (0.5 + random.random() * 0.5)
else:
sleep_time = min(delay * backoff_factor, max_delay)
time.sleep(sleep_time)Retry by Error Type
| Error Type | Retry? | Strategy |
|---|---|---|
| 401 Unauthorized | No | Fix credentials |
| 403 Forbidden | No | Check permissions |
| 404 Not Found | No | Check endpoint |
| 408 Timeout | Yes | Exponential backoff |
| 429 Rate Limit | Yes | Respect Retry-After |
| 500 Internal | Yes | Retry (3x max) |
| 502 Bad Gateway | Yes | Retry (3x max) |
| 503 Unavailable | Yes | Retry (5x max) |
| 504 Timeout | Yes | Retry (3x max) |
Idempotency
Most read operations (GET) are inherently idempotent. For write operations:
| Method | Idempotent? | Notes |
|---|---|---|
| GET | ✅ Yes | Safe to retry |
| POST | ❌ No | May create duplicates |
| PUT | ✅ Yes | Safe to retry |
| DELETE | ✅ Yes | Safe to retry |
Retrying POST
Be cautious when retrying POST requests as they may result in duplicate resources being created. Some endpoints support an Idempotency-Key header.
Rate Limit Headers
When rate limits are hit, the response includes helpful headers:
http
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705309200Use these headers to implement proper backoff.
Logging Best Practices
Error Log Format
python
logger.error(
"API request failed",
extra={
"error_type": type(error).__name__,
"status_code": getattr(error, "status_code", None),
"endpoint": endpoint,
"method": method,
"request_id": response.headers.get("X-Request-ID"),
"user_id": current_user.id,
"retry_count": retry_count,
"duration_ms": duration_ms
}
)Important Context to Log
- Request ID (
X-Request-IDheader) - User ID / API Key ID
- Endpoint and HTTP method
- Timestamp
- Error type and message
- Response status code
- Request duration
- Retry attempts
Troubleshooting Guide
Problem: 401 Unauthorized
Check:
- Is the Authorization header present?
- Is the scheme "Bearer" (not "Token", "Basic", etc.)?
- Is the token/key valid and not revoked?
- Is the user account active?
Fix:
python
# Correct header format
headers = {
"Authorization": "Bearer alai kis_sk_your_key_here"
}Problem: 400 Validation Error
Check:
- All required fields present?
- Field types correct?
- Values within allowed ranges?
- JSON properly formatted?
Debug:
python
import json
# Print validation errors
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 400:
print(json.dumps(response.json(), indent=2))Problem: 429 Rate Limit
Check:
- Review request frequency
- Check batch sizes
- Implement caching where possible
Fix:
python
# Respect Retry-After header
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)Problem: 500 Internal Error
Check:
- Is this a persistent issue or transient?
- Try the same request again
- Check Alaikis status page
Actions:
- Retry with exponential backoff
- Contact support if persistent
- Include X-Request-ID in support ticket
Problem: Packing Always Returns Empty
Check:
- Are item dimensions smaller than pallet dimensions?
- Are weight limits reasonable?
- Try with just one item
Debug:
python
# Test with simple case
payload = {
"skus": [{"id": "test", "length": 10, "width": 10, "height": 10, "quantity": 1}],
"pallets": [{"id": "pal1", "length": 100, "width": 100, "height": 100, "max_weight": 1000}]
}Getting Support
When contacting support, include:
- X-Request-ID: From response headers
- Timestamp: Exact time of the error
- Endpoint: Full URL path
- Request payload: Minified, if possible
- Response: Full error response
- User ID: Your user identifier
Support Email: support@alaikis.comStatus Page: https://status.alaikis.com