Skip to content

Error Handling Guide

Table of Contents

Overview

The Engagifii Training & Accreditation API uses standard HTTP status codes and a consistent error response format to help developers quickly identify and resolve issues. This guide provides comprehensive information about error handling, recovery strategies, and best practices for robust API integration.

Error Handling Principles

  1. Consistency: All errors follow the same response format
  2. Clarity: Error messages are clear and actionable
  3. Context: Errors include relevant context and debugging information
  4. Security: Sensitive information is never exposed in error messages
  5. Recoverability: Errors indicate whether retry is appropriate

Error Response Format

All API errors return a standardized JSON response structure:

Standard Error Response

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "target": "award.name",
    "details": [
      {
        "code": "FIELD_REQUIRED",
        "message": "Name is required",
        "target": "name"
      },
      {
        "code": "INVALID_FORMAT",
        "message": "Price must be a positive number",
        "target": "price",
        "value": -100
      }
    ],
    "innererror": {
      "code": "NullReferenceException",
      "trace": "Available in development mode only"
    }
  },
  "status": 400,
  "timestamp": "2024-01-20T10:30:45Z",
  "path": "/api/v1/Awards",
  "requestId": "req-123e4567-e89b-12d3-a456-426614174000",
  "tenantCode": "your-tenant-code"
}

Error Response Fields

FieldTypeDescriptionAlways Present
error.codestringMachine-readable error codeYes
error.messagestringHuman-readable error messageYes
error.targetstringField or parameter that caused the errorNo
error.detailsarrayAdditional error detailsNo
error.innererrorobjectDebugging information (dev mode only)No
statusintegerHTTP status codeYes
timestampstringISO 8601 timestampYes
pathstringAPI endpoint pathYes
requestIdstringUnique request identifierYes
tenantCodestringTenant identifierWhen authenticated

HTTP Status Codes

The API uses standard HTTP status codes grouped into categories:

2xx Success Codes

Status CodeMeaningUsage
200 OKRequest succeededGET, PUT, PATCH success
201 CreatedResource createdPOST success
202 AcceptedRequest accepted for processingAsync operations
204 No ContentSuccess with no response bodyDELETE success

3xx Redirection Codes

Status CodeMeaningUsage
301 Moved PermanentlyResource moved permanentlyAPI version migration
302 FoundResource temporarily movedTemporary redirect
304 Not ModifiedResource not modifiedCaching validation

4xx Client Error Codes

Status CodeMeaningCommon Causes
400 Bad RequestInvalid requestValidation errors, malformed JSON
401 UnauthorizedAuthentication requiredMissing/invalid token
403 ForbiddenAccess deniedInsufficient permissions
404 Not FoundResource not foundInvalid ID, deleted resource
405 Method Not AllowedInvalid HTTP methodPOST to GET-only endpoint
406 Not AcceptableCannot produce requested formatInvalid Accept header
409 ConflictResource conflictDuplicate key, version conflict
410 GoneResource permanently deletedAccessing deleted resource
412 Precondition FailedPrecondition not metIf-Match header mismatch
413 Payload Too LargeRequest too largeFile upload exceeds limit
415 Unsupported Media TypeInvalid content typeWrong Content-Type header
422 Unprocessable EntityBusiness rule violationLogic/validation errors
429 Too Many RequestsRate limit exceededToo many API calls

5xx Server Error Codes

Status CodeMeaningAction Required
500 Internal Server ErrorServer errorRetry with exponential backoff
501 Not ImplementedFeature not availableCheck API version
502 Bad GatewayGateway errorRetry after delay
503 Service UnavailableService down/maintenanceCheck status page, retry later
504 Gateway TimeoutRequest timeoutRetry with smaller payload

Error Categories

1. Validation Errors (400)

Occur when request data fails validation.

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more validation errors occurred",
    "details": [
      {
        "code": "REQUIRED_FIELD",
        "target": "email",
        "message": "Email address is required"
      },
      {
        "code": "INVALID_FORMAT",
        "target": "email",
        "message": "Email address format is invalid",
        "value": "not-an-email"
      },
      {
        "code": "STRING_TOO_LONG",
        "target": "description",
        "message": "Description exceeds maximum length of 2000 characters",
        "constraints": {
          "maxLength": 2000,
          "actualLength": 2500
        }
      }
    ]
  },
  "status": 400
}

Common Validation Error Codes:

  • REQUIRED_FIELD - Required field is missing
  • INVALID_FORMAT - Field format is invalid
  • INVALID_TYPE - Wrong data type provided
  • STRING_TOO_LONG - String exceeds max length
  • STRING_TOO_SHORT - String below min length
  • NUMBER_TOO_LARGE - Number exceeds maximum
  • NUMBER_TOO_SMALL - Number below minimum
  • INVALID_ENUM_VALUE - Value not in allowed list
  • INVALID_DATE_RANGE - End date before start date
  • FUTURE_DATE_REQUIRED - Date must be in future
  • DUPLICATE_VALUE - Value already exists

2. Authentication Errors (401)

Occur when authentication fails or is missing.

json
{
  "error": {
    "code": "AUTHENTICATION_REQUIRED",
    "message": "Authentication is required for this request",
    "details": [
      {
        "code": "MISSING_TENANT_CODE",
        "message": "The tenant-code header is required"
      }
    ]
  },
  "status": 401,
  "headers": {
    "WWW-Authenticate": "Bearer realm=\"api\", error=\"invalid_token\""
  }
}

Common Authentication Error Codes:

  • AUTHENTICATION_REQUIRED - No authentication provided
  • INVALID_TOKEN - Token is invalid or malformed
  • TOKEN_EXPIRED - Token has expired
  • MISSING_TENANT_CODE - Tenant code not provided
  • INVALID_TENANT_CODE - Tenant code not recognized
  • ACCOUNT_SUSPENDED - Account has been suspended
  • ACCOUNT_LOCKED - Too many failed attempts

3. Authorization Errors (403)

Occur when user lacks required permissions.

json
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "You do not have permission to perform this action",
    "details": [
      {
        "code": "ROLE_REQUIRED",
        "message": "This action requires the 'Administrator' role",
        "requiredRole": "Administrator",
        "userRoles": ["Member", "Viewer"]
      }
    ]
  },
  "status": 403
}

Common Authorization Error Codes:

  • INSUFFICIENT_PERMISSIONS - Lacks required permissions
  • ROLE_REQUIRED - Missing required role
  • SCOPE_REQUIRED - Missing required scope
  • ORGANIZATION_MISMATCH - Wrong organization context
  • FEATURE_NOT_ENABLED - Feature not enabled for tenant
  • SUBSCRIPTION_REQUIRED - Requires upgraded subscription

4. Resource Errors (404)

Occur when requested resource doesn't exist.

json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested resource was not found",
    "target": "awardId",
    "details": [
      {
        "code": "AWARD_NOT_FOUND",
        "message": "Award with ID '3fa85f64-5717-4562-b3fc-2c963f66afa6' was not found"
      }
    ]
  },
  "status": 404
}

Common Resource Error Codes:

  • RESOURCE_NOT_FOUND - Generic not found
  • AWARD_NOT_FOUND - Award doesn't exist
  • CLASS_NOT_FOUND - Class doesn't exist
  • MEMBER_NOT_FOUND - Member doesn't exist
  • ORGANIZATION_NOT_FOUND - Organization doesn't exist
  • ENDPOINT_NOT_FOUND - API endpoint doesn't exist
  • DELETED_RESOURCE - Resource has been deleted

5. Business Logic Errors (422)

Occur when request violates business rules.

json
{
  "error": {
    "code": "BUSINESS_RULE_VIOLATION",
    "message": "The operation violates business rules",
    "details": [
      {
        "code": "REGISTRATION_CLOSED",
        "message": "Registration for this class has closed",
        "closedDate": "2024-01-15T00:00:00Z"
      },
      {
        "code": "CAPACITY_EXCEEDED",
        "message": "Class is at full capacity",
        "capacity": 30,
        "enrolled": 30,
        "waitlistAvailable": true
      }
    ]
  },
  "status": 422
}

Common Business Error Codes:

  • REGISTRATION_CLOSED - Registration period ended
  • CAPACITY_EXCEEDED - Class/event is full
  • DUPLICATE_REGISTRATION - Already registered
  • PREREQUISITES_NOT_MET - Missing prerequisites
  • INSUFFICIENT_CREDITS - Not enough credits
  • APPROVAL_REQUIRED - Needs manager approval
  • BUDGET_EXCEEDED - Over budget limit
  • SCHEDULING_CONFLICT - Time conflict exists

6. Rate Limiting Errors (429)

Occur when API rate limits are exceeded.

json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "API rate limit exceeded",
    "details": [
      {
        "code": "REQUESTS_PER_MINUTE",
        "message": "Maximum 1000 requests per minute exceeded",
        "limit": 1000,
        "remaining": 0,
        "resetTime": "2024-01-20T10:31:00Z"
      }
    ]
  },
  "status": 429,
  "headers": {
    "X-RateLimit-Limit": "1000",
    "X-RateLimit-Remaining": "0",
    "X-RateLimit-Reset": "1705749060",
    "Retry-After": "60"
  }
}

7. Server Errors (500)

Occur due to server-side issues.

json
{
  "error": {
    "code": "INTERNAL_SERVER_ERROR",
    "message": "An unexpected error occurred",
    "requestId": "req-123e4567-e89b-12d3-a456-426614174000",
    "timestamp": "2024-01-20T10:30:45Z",
    "details": [
      {
        "code": "DATABASE_ERROR",
        "message": "A database error occurred. Please try again."
      }
    ]
  },
  "status": 500,
  "support": {
    "message": "If this problem persists, contact support with request ID",
    "url": "https://support.engagifii.com",
    "email": "api-support@engagifii.com"
  }
}

Error Resolution

Resolution Strategies by Error Type

Validation Errors (400)

  1. Check the details array for specific field errors
  2. Validate input against schema documentation
  3. Ensure required fields are present
  4. Verify data types and formats
  5. Check field length constraints
javascript
function handleValidationError(error) {
  const validationErrors = error.details || [];
  
  validationErrors.forEach(detail => {
    console.log(`Field: ${detail.target}`);
    console.log(`Issue: ${detail.message}`);
    
    // Apply field-specific fixes
    switch (detail.code) {
      case 'REQUIRED_FIELD':
        // Add missing field
        break;
      case 'INVALID_FORMAT':
        // Fix format
        break;
      case 'STRING_TOO_LONG':
        // Truncate string
        break;
    }
  });
}

Authentication Errors (401)

  1. Verify tenant code is included in headers
  2. Check token expiration
  3. Refresh token if expired
  4. Re-authenticate if refresh fails
javascript
async function handleAuthError(error, retryRequest) {
  switch (error.code) {
    case 'TOKEN_EXPIRED':
      const newToken = await refreshToken();
      if (newToken) {
        return retryRequest(newToken);
      }
      break;
      
    case 'MISSING_TENANT_CODE':
      // Add tenant code to request
      break;
      
    case 'INVALID_TOKEN':
      // Re-authenticate
      await authenticate();
      break;
  }
}

Rate Limiting Errors (429)

  1. Read the Retry-After header
  2. Implement exponential backoff
  3. Queue requests if necessary
  4. Consider request batching
javascript
async function handleRateLimit(error, retryRequest) {
  const retryAfter = error.headers?.['Retry-After'] || 60;
  const resetTime = error.headers?.['X-RateLimit-Reset'];
  
  console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
  
  await new Promise(resolve => 
    setTimeout(resolve, retryAfter * 1000)
  );
  
  return retryRequest();
}

Retry Strategies

Exponential Backoff

Implement exponential backoff for transient errors:

javascript
class RetryManager {
  constructor(maxRetries = 3, baseDelay = 1000) {
    this.maxRetries = maxRetries;
    this.baseDelay = baseDelay;
  }
  
  async executeWithRetry(fn, shouldRetry = this.defaultShouldRetry) {
    let lastError;
    
    for (let attempt = 0; attempt < this.maxRetries; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error;
        
        if (!shouldRetry(error, attempt)) {
          throw error;
        }
        
        const delay = this.calculateDelay(attempt);
        console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
        
        await this.wait(delay);
      }
    }
    
    throw lastError;
  }
  
  calculateDelay(attempt) {
    // Exponential backoff with jitter
    const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
    const jitter = Math.random() * 1000;
    return Math.min(exponentialDelay + jitter, 30000); // Max 30 seconds
  }
  
  defaultShouldRetry(error, attempt) {
    // Retry on server errors and rate limits
    const retryableStatuses = [429, 500, 502, 503, 504];
    const status = error.response?.status;
    
    return retryableStatuses.includes(status) && attempt < this.maxRetries - 1;
  }
  
  wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const retry = new RetryManager();
const result = await retry.executeWithRetry(async () => {
  return await apiClient.get('/api/v1/Awards/List');
});

Circuit Breaker Pattern

Prevent cascading failures with circuit breaker:

javascript
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold;
    this.timeout = timeout;
    this.failures = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }
  
  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }
  
  onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();
    
    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
      console.log('Circuit breaker opened due to failures');
    }
  }
}

Logging and Debugging

Request ID Tracking

Always log the request ID for troubleshooting:

javascript
class ApiLogger {
  logError(error) {
    const requestId = error.requestId || 'unknown';
    const timestamp = error.timestamp || new Date().toISOString();
    
    console.error({
      level: 'ERROR',
      requestId: requestId,
      timestamp: timestamp,
      status: error.status,
      code: error.error?.code,
      message: error.error?.message,
      path: error.path,
      tenantCode: error.tenantCode
    });
    
    // Send to monitoring service
    this.sendToMonitoring(error);
  }
  
  sendToMonitoring(error) {
    // Integration with monitoring service
    // e.g., Sentry, DataDog, New Relic
  }
}

Debug Information

Enable debug mode for detailed error information:

javascript
class DebugErrorHandler {
  constructor(debugMode = false) {
    this.debugMode = debugMode;
  }
  
  formatError(error) {
    const formatted = {
      message: error.error?.message,
      code: error.error?.code,
      status: error.status,
      requestId: error.requestId
    };
    
    if (this.debugMode) {
      formatted.debug = {
        timestamp: error.timestamp,
        path: error.path,
        details: error.error?.details,
        innererror: error.error?.innererror,
        headers: error.headers,
        request: {
          method: error.request?.method,
          url: error.request?.url,
          body: error.request?.body
        }
      };
    }
    
    return formatted;
  }
}

Client Implementation

Complete Error Handler

javascript
class ApiErrorHandler {
  constructor(options = {}) {
    this.retryManager = new RetryManager();
    this.circuitBreaker = new CircuitBreaker();
    this.logger = new ApiLogger();
    this.options = options;
  }
  
  async handleError(error, context = {}) {
    // Log the error
    this.logger.logError(error);
    
    // Extract error details
    const status = error.response?.status;
    const errorCode = error.response?.data?.error?.code;
    
    // Handle based on status code
    switch (status) {
      case 400:
        return this.handleValidationError(error);
        
      case 401:
        return this.handleAuthenticationError(error);
        
      case 403:
        return this.handleAuthorizationError(error);
        
      case 404:
        return this.handleNotFoundError(error);
        
      case 409:
        return this.handleConflictError(error);
        
      case 422:
        return this.handleBusinessError(error);
        
      case 429:
        return this.handleRateLimitError(error);
        
      case 500:
      case 502:
      case 503:
      case 504:
        return this.handleServerError(error);
        
      default:
        return this.handleUnknownError(error);
    }
  }
  
  handleValidationError(error) {
    const details = error.response?.data?.error?.details || [];
    const fieldErrors = {};
    
    details.forEach(detail => {
      fieldErrors[detail.target] = detail.message;
    });
    
    return {
      type: 'VALIDATION',
      message: 'Please correct the validation errors',
      fieldErrors: fieldErrors,
      recoverable: true
    };
  }
  
  handleAuthenticationError(error) {
    return {
      type: 'AUTHENTICATION',
      message: 'Authentication required',
      action: 'REAUTHENTICATE',
      recoverable: true
    };
  }
  
  handleRateLimitError(error) {
    const retryAfter = error.response?.headers?.['retry-after'];
    
    return {
      type: 'RATE_LIMIT',
      message: 'Too many requests',
      retryAfter: retryAfter,
      recoverable: true
    };
  }
  
  handleServerError(error) {
    return {
      type: 'SERVER_ERROR',
      message: 'Server error occurred',
      requestId: error.response?.data?.requestId,
      recoverable: true,
      retry: true
    };
  }
  
  getUserMessage(error) {
    // Return user-friendly messages
    const messages = {
      'VALIDATION': 'Please check your input and try again',
      'AUTHENTICATION': 'Please log in to continue',
      'AUTHORIZATION': 'You don\'t have permission for this action',
      'NOT_FOUND': 'The requested item was not found',
      'RATE_LIMIT': 'Please wait a moment before trying again',
      'SERVER_ERROR': 'Something went wrong. Please try again later'
    };
    
    return messages[error.type] || 'An error occurred';
  }
}

Error Boundary Component (React)

jsx
class ApiErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
    this.errorHandler = new ApiErrorHandler();
  }
  
  componentDidCatch(error, errorInfo) {
    if (error.isApiError) {
      const handled = this.errorHandler.handleError(error);
      
      this.setState({
        hasError: true,
        error: handled
      });
      
      if (handled.recoverable) {
        this.scheduleRetry(handled);
      }
    }
  }
  
  scheduleRetry(error) {
    if (error.retryAfter) {
      setTimeout(() => {
        this.retry();
      }, error.retryAfter * 1000);
    }
  }
  
  retry() {
    this.setState({ hasError: false, error: null });
    this.props.onRetry?.();
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <ErrorDisplay
          error={this.state.error}
          onRetry={() => this.retry()}
        />
      );
    }
    
    return this.props.children;
  }
}

Best Practices

1. Always Handle Errors Gracefully

  • Never show raw error messages to users
  • Provide actionable error messages
  • Log detailed errors for debugging

2. Implement Retry Logic

  • Use exponential backoff for transient errors
  • Set maximum retry limits
  • Don't retry client errors (4xx)

3. Monitor Error Patterns

  • Track error rates by endpoint
  • Alert on error rate spikes
  • Analyze error patterns for improvements

4. Provide Context

  • Include request IDs in support requests
  • Log sufficient context for debugging
  • Maintain error audit trails

5. Test Error Scenarios

  • Test all error conditions
  • Verify error recovery mechanisms
  • Ensure graceful degradation

6. Document Known Issues

  • Maintain a list of known errors
  • Document workarounds
  • Update documentation regularly

For additional support, include the request ID from error responses when contacting support.