Error Handling

The Loyalty.lt API uses conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error that failed given the information provided, and codes in the 5xx range indicate an error with our servers.
All error responses include a detailed error message and unique request ID to help with debugging and support.

Response Structure

Success Response

{
  "success": true,
  "code": 200,
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Operation completed successfully",
  "data": {
    // Response data here
  }
}

Error Response

{
  "success": false,
  "code": 400,
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Validation failed",
  "errors": [
    {
      "field": "email",
      "message": "Email address is required",
      "code": "REQUIRED_FIELD"
    }
  ]
}

HTTP Status Codes

2xx Success

CodeStatusDescription
200OKRequest succeeded
201CreatedResource was successfully created
202AcceptedRequest accepted for processing
204No ContentRequest succeeded with no response body

4xx Client Errors

The request was unacceptable, often due to missing or invalid parameters.
{
  "success": false,
  "code": 400,
  "request_id": "req_123",
  "message": "Invalid request parameters",
  "errors": [
    {
      "field": "points",
      "message": "Points must be a positive integer",
      "code": "INVALID_VALUE"
    }
  ]
}
Common Causes:
  • Invalid JSON in request body
  • Missing required parameters
  • Invalid parameter types or formats
  • Request body too large

5xx Server Errors

CodeStatusDescription
500Internal Server ErrorSomething went wrong on our end
502Bad GatewayInvalid response from upstream server
503Service UnavailableService temporarily overloaded or down
504Gateway TimeoutRequest timed out

Error Codes

Each error includes a specific error code for programmatic handling:

Authentication Errors

  • INVALID_CREDENTIALS - API key or secret is invalid
  • EXPIRED_TOKEN - JWT token has expired
  • MISSING_AUTHENTICATION - No authentication provided
  • INVALID_TOKEN_FORMAT - Malformed JWT token

Authorization Errors

  • INSUFFICIENT_PERMISSIONS - Lacking required permissions
  • ACCESS_DENIED - Resource access denied
  • IP_NOT_WHITELISTED - IP address not allowed
  • SCOPE_INSUFFICIENT - API key scope limitations

Validation Errors

  • REQUIRED_FIELD - Required field is missing
  • INVALID_VALUE - Field value is invalid
  • DUPLICATE_VALUE - Value already exists
  • INVALID_FORMAT - Field format is incorrect
  • OUT_OF_RANGE - Value outside allowed range

Business Logic Errors

  • INSUFFICIENT_BALANCE - Not enough points/credits
  • RESOURCE_NOT_FOUND - Requested resource doesn’t exist
  • INVALID_STATE - Operation not allowed in current state
  • QUOTA_EXCEEDED - Usage quota exceeded
  • EXPIRED_OFFER - Offer or coupon has expired

Rate Limiting Errors

  • RATE_LIMIT_EXCEEDED - Too many requests
  • DAILY_QUOTA_EXCEEDED - Daily API quota reached
  • CONCURRENT_LIMIT_EXCEEDED - Too many concurrent requests

Error Handling Best Practices

Implement Robust Error Handling

const axios = require('axios');

async function makeAPICall() {
  try {
    const response = await axios.post('https://staging-api.loyalty.lt/api/customers', {
      email: 'customer@example.com',
      first_name: 'John'
    }, {
      headers: {
        'X-API-Key': process.env.LOYALTY_API_KEY,
        'X-API-Secret': process.env.LOYALTY_API_SECRET
      }
    });
    
    return response.data;
  } catch (error) {
    if (error.response) {
      // API returned an error response
      const { status, data } = error.response;
      
      switch (status) {
        case 400:
          console.error('Bad Request:', data.message);
          break;
        case 401:
          console.error('Authentication failed:', data.message);
          // Refresh credentials or redirect to login
          break;
        case 422:
          console.error('Validation errors:');
          data.errors.forEach(err => {
            console.error(`- ${err.field}: ${err.message}`);
          });
          break;
        case 429:
          console.error('Rate limited. Retry after:', error.response.headers['retry-after']);
          // Implement exponential backoff
          break;
        default:
          console.error('API Error:', data.message);
      }
    } else if (error.request) {
      // Network error
      console.error('Network error:', error.message);
    } else {
      // Other error
      console.error('Error:', error.message);
    }
    throw error;
  }
}

Implement Exponential Backoff

For rate limiting and server errors, implement exponential backoff:
async function exponentialBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.response?.status === 429 || error.response?.status >= 500) {
        if (attempt === maxRetries) throw error;
        
        const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
        const jitter = Math.random() * 1000;
        await new Promise(resolve => setTimeout(resolve, delay + jitter));
      } else {
        throw error;
      }
    }
  }
}

Log Errors with Context

Always log errors with sufficient context for debugging:
function logAPIError(error, context) {
  const logData = {
    timestamp: new Date().toISOString(),
    context,
    request_id: error.response?.data?.request_id,
    status: error.response?.status,
    message: error.response?.data?.message,
    errors: error.response?.data?.errors
  };
  
  console.error('API Error:', JSON.stringify(logData, null, 2));
}

Common Error Scenarios

Insufficient Points Balance

{
  "success": false,
  "code": 422,
  "message": "Cannot redeem points",
  "errors": [
    {
      "field": "points",
      "message": "Customer has 150 points but 200 points requested",
      "code": "INSUFFICIENT_BALANCE",
      "meta": {
        "available_points": 150,
        "requested_points": 200
      }
    }
  ]
}
Handling:
if (error.response?.data?.errors?.[0]?.code === 'INSUFFICIENT_BALANCE') {
  const available = error.response.data.errors[0].meta.available_points;
  console.log(`Customer only has ${available} points available`);
  // Update UI to show available points
}

Expired Offers

{
  "success": false,
  "code": 422,
  "message": "Cannot redeem offer",
  "errors": [
    {
      "field": "offer_id",
      "message": "Offer expired on 2024-01-15",
      "code": "EXPIRED_OFFER",
      "meta": {
        "expired_at": "2024-01-15T23:59:59Z"
      }
    }
  ]
}

Duplicate Customer Registration

{
  "success": false,
  "code": 422,
  "message": "Customer already exists",
  "errors": [
    {
      "field": "email",
      "message": "Email address is already registered",
      "code": "DUPLICATE_VALUE",
      "meta": {
        "existing_customer_id": "cust_123"
      }
    }
  ]
}

Webhooks Error Handling

When receiving webhooks, always return appropriate HTTP status codes:
app.post('/webhooks/loyalty', (req, res) => {
  try {
    // Verify webhook signature
    if (!verifyWebhookSignature(req.body, req.headers['x-signature'])) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    // Process webhook
    processLoyaltyEvent(req.body);
    
    // Return success
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Webhook processing failed:', error);
    // Return 5xx to trigger retry
    res.status(500).json({ error: 'Processing failed' });
  }
});

Testing Error Scenarios

Simulate Errors in Staging

Use these techniques to test error handling:
# Test validation errors
curl -X POST "https://staging-api.loyalty.lt/api/customers" \
  -H "X-API-Key: your_key" \
  -H "X-API-Secret: your_secret" \
  -d '{"email": "invalid-email"}'

# Test authentication errors  
curl -X GET "https://staging-api.loyalty.lt/api/customers" \
  -H "X-API-Key: invalid_key"

# Test rate limiting (make many rapid requests)
for i in {1..100}; do
  curl -X GET "https://staging-api.loyalty.lt/api/customers"
done

Error Monitoring

Set up monitoring for common error patterns:
// Monitor error rates
function trackAPIError(error) {
  const metrics = {
    error_type: error.response?.data?.errors?.[0]?.code || 'UNKNOWN',
    status_code: error.response?.status,
    endpoint: error.config?.url,
    timestamp: Date.now()
  };
  
  // Send to monitoring service
  analytics.track('api_error', metrics);
}

Support & Debugging

When reporting errors, include the request ID, timestamp, endpoint URL, and request/response bodies (excluding sensitive data).