Skip to 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:

json
{
    "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:

json
{
    "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

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

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

http
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

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

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

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:

  1. Review request frequency
  2. Check batch sizes
  3. 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:

  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:

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:

  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.comStatus Page: https://status.alaikis.com