Skip to main content

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:

{
"detail": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {}
}
}

Error Object Fields:

FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
detailsobjectAdditional context (optional)

For simpler validation errors, FastAPI returns:

{
"detail": [
{
"loc": ["body", "sku"],
"msg": "field required",
"type": "value_error.missing"
}
]
}

HTTP Status Codes​

Status CodeCategoryDescription
200SuccessRequest completed successfully
201SuccessResource created successfully
202SuccessRequest accepted, processing async
400Client ErrorBad request - validation failed
401Client ErrorUnauthorized - authentication required
403Client ErrorForbidden - insufficient permissions
404Client ErrorNot Found - resource doesn't exist
409Client ErrorConflict - resource state conflict
422Client ErrorUnprocessable Entity - validation
429Client ErrorToo Many Requests - rate limit hit
500Server ErrorInternal server error
502Server ErrorBad Gateway
503Server ErrorService Unavailable
504Server ErrorGateway Timeout

Common Error Codes​

Authentication Errors​

Error CodeHTTP StatusDescription
auth_missing_header401Authorization header missing
auth_invalid_token401Invalid or malformed token
auth_token_expired401JWT token has expired
auth_key_revoked401API key has been revoked
auth_invalid_scheme401Invalid auth scheme (use Bearer)
auth_user_inactive401User account is deactivated

Validation Errors​

Error CodeHTTP StatusDescription
validation_failed400Request validation failed
missing_required_field400Required field missing
invalid_format400Invalid field format
value_out_of_range400Value outside allowed range
invalid_enum_value400Value not in allowed enumeration

Resource Errors​

Error CodeHTTP StatusDescription
resource_not_found404Requested resource not found
resource_conflict409Resource state conflict
resource_already_exists409Resource already exists

Rate Limiting​

Error CodeHTTP StatusDescription
rate_limit_exceeded429Too many requests
concurrency_limit_exceeded429Too many concurrent requests

Service Errors​

Error CodeHTTP StatusDescription
service_unavailable503Service temporarily unavailable
internal_error500Unexpected server error
timeout_error504Request timed out

Packing Service Errors​

Error CodeHTTP StatusDescription
packing_no_items400No items to pack
packing_item_too_large400Item exceeds all pallet dimensions
packing_no_pallets400No pallets available
packing_unsolvable400Cannot create valid packing

Product Trace Errors​

Error CodeHTTP StatusDescription
trace_no_features400No features extracted from image
trace_barcode_not_found400No barcode detected
trace_image_too_small400Image resolution too low
trace_async_pending202Processing not complete yet

Address Service Errors​

Error CodeHTTP StatusDescription
address_libpostal_not_installed503libpostal not available
address_parse_failed400Could not parse address
address_empty_input400Empty address string

Handling Errors in Code​

Python Example​

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​

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​

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 TypeRetry?Strategy
401 UnauthorizedNoFix credentials
403 ForbiddenNoCheck permissions
404 Not FoundNoCheck endpoint
408 TimeoutYesExponential backoff
429 Rate LimitYesRespect Retry-After
500 InternalYesRetry (3x max)
502 Bad GatewayYesRetry (3x max)
503 UnavailableYesRetry (5x max)
504 TimeoutYesRetry (3x max)

Idempotency​

Most read operations (GET) are inherently idempotent. For write operations:

MethodIdempotent?Notes
GET✅ YesSafe to retry
POST❌ NoMay create duplicates
PUT✅ YesSafe to retry
DELETE✅ YesSafe 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:

Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705309200

Use these headers to implement proper backoff.

Logging Best Practices​

Error Log Format​

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-ID header)
  • 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:

  1. Is the Authorization header present?
  2. Is the scheme "Bearer" (not "Token", "Basic", etc.)?
  3. Is the token/key valid and not revoked?
  4. Is the user account active?

Fix:

# Correct header format
headers = {
"Authorization": "Bearer alai kis_sk_your_key_here"
}

Problem: 400 Validation Error​

Check:

  1. All required fields present?
  2. Field types correct?
  3. Values within allowed ranges?
  4. JSON properly formatted?

Debug:

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:

  1. Review request frequency
  2. Check batch sizes
  3. Implement caching where possible

Fix:

# Respect Retry-After header
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)

Problem: 500 Internal Error​

Check:

  1. Is this a persistent issue or transient?
  2. Try the same request again
  3. 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:

  1. Are item dimensions smaller than pallet dimensions?
  2. Are weight limits reasonable?
  3. Try with just one item

Debug:

# 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:

  1. X-Request-ID: From response headers
  2. Timestamp: Exact time of the error
  3. Endpoint: Full URL path
  4. Request payload: Minified, if possible
  5. Response: Full error response
  6. User ID: Your user identifier

Support Email: support@alaikis.com Status Page: https://status.alaikis.com