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
| Code | Status | Description | 
|---|
| 200 | OK | Request succeeded | 
| 201 | Created | Resource was successfully created | 
| 202 | Accepted | Request accepted for processing | 
| 204 | No Content | Request succeeded with no response body | 
 
4xx Client Errors
 400 Bad Request
 401 Unauthorized
 403 Forbidden
 404 Not Found
 422 Validation Error
 429 Rate Limited
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
| Code | Status | Description | 
|---|
| 500 | Internal Server Error | Something went wrong on our end | 
| 502 | Bad Gateway | Invalid response from upstream server | 
| 503 | Service Unavailable | Service temporarily overloaded or down | 
| 504 | Gateway Timeout | Request 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).