Skip to content

Error Handling Guide

Table of Contents

Overview

The Engagifii CRM API uses standard HTTP status codes and provides detailed error information to help developers diagnose and resolve issues quickly. All errors return structured JSON responses with consistent formatting across all endpoints.

Key Error Handling Features

  • Consistent Error Format: Standardized error response structure
  • Detailed Error Information: Specific error codes, messages, and resolution guidance
  • Field-Level Validation: Detailed validation errors for form data
  • Request Tracking: Unique request IDs for error correlation
  • Rate Limit Information: Clear rate limiting feedback
  • Structured Error Codes: Machine-readable error classification

Error Response Format

All error responses follow this consistent structure:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid data",
    "details": [
      {
        "field": "email",
        "message": "Email address is required",
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "phone",
        "message": "Phone number format is invalid",
        "code": "INVALID_FORMAT"
      }
    ],
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-123e4567-e89b-12d3-a456-426614174000",
    "path": "/api/v1/people",
    "method": "POST"
  }
}

Error Response Properties

  • code: Machine-readable error identifier for programmatic handling
  • message: Human-readable error description
  • details: Array of specific error details (for validation errors)
    • field: Name of the field that caused the error
    • message: Specific error message for the field
    • code: Field-specific error code
  • timestamp: ISO 8601 timestamp when error occurred
  • requestId: Unique identifier for the request (useful for support)
  • path: API endpoint that generated the error
  • method: HTTP method used

HTTP Status Codes

Success Codes (2xx)

CodeDescriptionUsage
200 OKRequest successfulGET requests, successful updates
201 CreatedResource createdPOST requests that create new resources
204 No ContentRequest successful, no contentDELETE requests, some PUT requests

Client Error Codes (4xx)

CodeDescriptionCommon CausesResolution
400 Bad RequestInvalid request formatMissing required fields, invalid JSON, validation errorsCheck request format and required fields
401 UnauthorizedAuthentication requiredMissing or invalid access tokenObtain valid access token
403 ForbiddenAccess deniedInvalid tenant code, insufficient permissionsVerify tenant code and permissions
404 Not FoundResource not foundInvalid ID, deleted resourceCheck resource ID exists
405 Method Not AllowedHTTP method not supportedUsing GET instead of POSTUse correct HTTP method
409 ConflictResource conflictDuplicate email, circular relationshipsResolve data conflicts
422 Unprocessable EntityValidation errorBusiness rule violationsFix validation errors
429 Too Many RequestsRate limit exceededToo many requestsImplement rate limiting

Server Error Codes (5xx)

CodeDescriptionAction
500 Internal Server ErrorUnexpected server errorContact support with request ID
502 Bad GatewayGateway errorRetry request, contact support if persists
503 Service UnavailableService temporarily unavailableWait and retry, check status page
504 Gateway TimeoutRequest timeoutRetry request with longer timeout

Error Categories

1. Authentication Errors

Authentication-related errors when accessing protected resources.

Example Response:

json
{
  "error": {
    "code": "INVALID_TOKEN",
    "message": "The access token has expired",
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-auth-001"
  }
}

Common Authentication Error Codes:

  • MISSING_TOKEN: Authorization header not provided
  • INVALID_TOKEN: Token format is invalid or corrupted
  • EXPIRED_TOKEN: Access token has expired
  • INSUFFICIENT_SCOPE: Token lacks required permissions

Resolution:

  1. Verify the Authorization header is included
  2. Check token format includes "Bearer " prefix
  3. Obtain a new access token if expired
  4. Verify client has required scopes/permissions

2. Authorization Errors

Errors related to tenant access and permissions.

Example Response:

json
{
  "error": {
    "code": "INVALID_TENANT",
    "message": "The specified tenant code is not valid or access is denied",
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-tenant-001"
  }
}

Common Authorization Error Codes:

  • MISSING_TENANT_CODE: tenant-code header not provided
  • INVALID_TENANT: Tenant code is invalid or inactive
  • ACCESS_DENIED: User lacks permission for requested operation
  • TENANT_SUSPENDED: Tenant account is suspended

Resolution:

  1. Include valid tenant-code header in all requests
  2. Verify tenant code with system administrator
  3. Check user permissions for the operation
  4. Contact support for tenant account issues

3. Validation Errors

Detailed validation errors for request data.

Example Response:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request contains validation errors",
    "details": [
      {
        "field": "firstName",
        "message": "First name is required",
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "email",
        "message": "Email format is invalid",
        "code": "INVALID_FORMAT"
      },
      {
        "field": "phone",
        "message": "Phone number must be in international format",
        "code": "INVALID_FORMAT"
      }
    ],
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-validation-001"
  }
}

Common Validation Error Codes:

  • REQUIRED_FIELD: Required field is missing or empty
  • INVALID_FORMAT: Field format is incorrect (email, phone, date, etc.)
  • INVALID_LENGTH: Field length exceeds limits
  • INVALID_VALUE: Field value is not in acceptable range
  • DUPLICATE_VALUE: Value must be unique but already exists
  • INVALID_RELATIONSHIP: Referenced entity doesn't exist or is invalid

4. Business Logic Errors

Errors related to business rules and constraints.

Example Response:

json
{
  "error": {
    "code": "BUSINESS_RULE_VIOLATION",
    "message": "Cannot delete organization with active members",
    "details": [
      {
        "field": "memberCount",
        "message": "Organization has 25 active members that must be reassigned first",
        "code": "HAS_ACTIVE_MEMBERS"
      }
    ],
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-business-001"
  }
}

Common Business Rule Error Codes:

  • CIRCULAR_RELATIONSHIP: Would create circular reference
  • HAS_ACTIVE_MEMBERS: Cannot delete entity with active associated records
  • INVALID_DATE_RANGE: End date must be after start date
  • DUPLICATE_MEMBERSHIP: Person already member of committee/organization
  • CAPACITY_EXCEEDED: Maximum capacity reached

5. Resource Errors

Errors related to resource availability and access.

Example Response:

json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested person was not found",
    "details": [
      {
        "field": "id", 
        "message": "Person with ID '123e4567-e89b-12d3-a456-426614174000' does not exist",
        "code": "INVALID_ID"
      }
    ],
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-resource-001"
  }
}

Common Resource Error Codes:

  • RESOURCE_NOT_FOUND: Requested resource doesn't exist
  • RESOURCE_DELETED: Resource was soft-deleted
  • RESOURCE_INACTIVE: Resource exists but is inactive
  • INVALID_ID: ID format is invalid or malformed

6. Rate Limiting Errors

Errors when API rate limits are exceeded.

Example Response:

json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Request rate limit exceeded",
    "details": [
      {
        "field": "rateLimit",
        "message": "Maximum 1000 requests per minute exceeded",
        "code": "REQUESTS_PER_MINUTE_EXCEEDED"
      }
    ],
    "timestamp": "2024-01-25T10:30:00Z",
    "requestId": "req-rate-001"
  }
}

Rate Limit Headers:

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

Common Error Scenarios

Scenario 1: Invalid Authentication

Request:

bash
curl -X GET "https://builtin-crm.azurewebsites.net/api/v1/people" \
  -H "tenant-code: my-tenant"

Response: 401 Unauthorized

json
{
  "error": {
    "code": "MISSING_TOKEN",
    "message": "Authorization header is required"
  }
}

Solution: Add Authorization header with valid bearer token.

Scenario 2: Malformed Request Body

Request:

bash
curl -X POST "https://builtin-crm.azurewebsites.net/api/v1/people" \
  -H "Authorization: Bearer token123" \
  -H "tenant-code: my-tenant" \
  -H "Content-Type: application/json" \
  -d '{"firstName": "", "email": "invalid-email"}'

Response: 400 Bad Request

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request contains validation errors",
    "details": [
      {
        "field": "firstName",
        "message": "First name is required",
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "lastName",
        "message": "Last name is required", 
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "email",
        "message": "Email format is invalid",
        "code": "INVALID_FORMAT"
      }
    ]
  }
}

Solution: Provide all required fields with valid formats.

Scenario 3: Resource Not Found

Request:

bash
curl -X GET "https://builtin-crm.azurewebsites.net/api/v1/people/nonexistent-id" \
  -H "Authorization: Bearer token123" \
  -H "tenant-code: my-tenant"

Response: 404 Not Found

json
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Person not found",
    "details": [
      {
        "field": "id",
        "message": "Person with ID 'nonexistent-id' does not exist",
        "code": "INVALID_ID"
      }
    ]
  }
}

Solution: Verify the resource ID exists and is accessible to your tenant.

Error Resolution Strategies

1. Validation Error Resolution

When receiving validation errors:

javascript
function handleValidationError(errorResponse) {
  const validationErrors = {};
  
  if (errorResponse.error.details) {
    errorResponse.error.details.forEach(detail => {
      validationErrors[detail.field] = {
        message: detail.message,
        code: detail.code
      };
    });
  }
  
  // Display field-specific errors to user
  Object.keys(validationErrors).forEach(field => {
    displayFieldError(field, validationErrors[field].message);
  });
}

2. Authentication Error Recovery

javascript
async function handleAuthError(errorResponse, originalRequest) {
  if (errorResponse.error.code === 'EXPIRED_TOKEN') {
    try {
      // Attempt to refresh token
      const newToken = await refreshAccessToken();
      
      // Retry original request with new token
      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return await retryRequest(originalRequest);
    } catch (refreshError) {
      // Redirect to login if refresh fails
      redirectToLogin();
    }
  }
}

3. Business Rule Error Handling

javascript
function handleBusinessRuleError(errorResponse) {
  switch (errorResponse.error.code) {
    case 'HAS_ACTIVE_MEMBERS':
      // Offer to reassign members before deletion
      showMemberReassignmentDialog();
      break;
      
    case 'DUPLICATE_VALUE':
      // Suggest alternatives or allow user to update existing
      showDuplicateResolutionOptions();
      break;
      
    case 'CIRCULAR_RELATIONSHIP':
      // Explain the issue and suggest valid options
      showCircularRelationshipWarning();
      break;
  }
}

Retry Logic

Exponential Backoff Strategy

Implement exponential backoff for transient errors:

javascript
async function apiRequestWithRetry(requestConfig, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await makeApiRequest(requestConfig);
      return response;
    } catch (error) {
      lastError = error;
      
      // Don't retry client errors (4xx) except 429
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error;
      }
      
      // Don't retry on last attempt
      if (attempt === maxRetries) {
        throw error;
      }
      
      // Calculate delay with exponential backoff
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
      
      // Add jitter to prevent thundering herd
      const jitter = Math.random() * 1000;
      
      await sleep(delay + jitter);
    }
  }
  
  throw lastError;
}

Rate Limit Handling

javascript
async function handleRateLimit(error, requestConfig) {
  if (error.status === 429) {
    const retryAfter = error.headers['retry-after'];
    const resetTime = error.headers['x-ratelimit-reset'];
    
    let waitTime;
    if (retryAfter) {
      waitTime = parseInt(retryAfter) * 1000; // Convert to milliseconds
    } else if (resetTime) {
      waitTime = (parseInt(resetTime) * 1000) - Date.now();
    } else {
      waitTime = 60000; // Default 1 minute
    }
    
    console.log(`Rate limited. Waiting ${waitTime}ms before retry`);
    await sleep(waitTime);
    
    return await makeApiRequest(requestConfig);
  }
  
  throw error;
}

Debugging and Logging

Request/Response Logging

Log requests and responses for debugging (without sensitive data):

javascript
function logApiRequest(config, response, error) {
  const logData = {
    timestamp: new Date().toISOString(),
    method: config.method,
    url: config.url,
    requestId: response?.headers?.['x-request-id'] || error?.response?.data?.error?.requestId,
    status: response?.status || error?.response?.status,
    duration: response?.config?.metadata?.endTime - response?.config?.metadata?.startTime
  };
  
  if (error) {
    logData.error = {
      code: error.response?.data?.error?.code,
      message: error.response?.data?.error?.message
    };
  }
  
  console.log('API Request:', logData);
}

Error Correlation

Use request IDs to correlate errors across systems:

javascript
function trackError(error, context) {
  const errorInfo = {
    requestId: error.response?.data?.error?.requestId,
    timestamp: error.response?.data?.error?.timestamp,
    userAgent: navigator.userAgent,
    url: window.location.href,
    userId: context.userId,
    tenantCode: context.tenantCode
  };
  
  // Send to error tracking service
  errorTracker.captureError(error, errorInfo);
}

Client-Side Error Handling

javascript
// Global error handler for API requests
axios.interceptors.response.use(
  response => response,
  error => {
    // Log error details
    console.error('API Error:', {
      status: error.response?.status,
      code: error.response?.data?.error?.code,
      message: error.response?.data?.error?.message,
      requestId: error.response?.data?.error?.requestId
    });
    
    // Handle common errors
    switch (error.response?.status) {
      case 401:
        handleAuthError(error);
        break;
      case 403:
        handleAuthorizationError(error);
        break;
      case 429:
        return handleRateLimit(error);
      case 500:
      case 502:
      case 503:
      case 504:
        return handleServerError(error);
    }
    
    return Promise.reject(error);
  }
);

Error Monitoring

Set up monitoring for error patterns:

javascript
function monitorErrorPatterns() {
  // Track error rates
  const errorMetrics = {
    '4xx': 0,
    '5xx': 0,
    'auth_errors': 0,
    'validation_errors': 0,
    'rate_limit_errors': 0
  };
  
  // Update metrics on each error
  function updateErrorMetrics(error) {
    const status = error.response?.status;
    const code = error.response?.data?.error?.code;
    
    if (status >= 400 && status < 500) {
      errorMetrics['4xx']++;
    } else if (status >= 500) {
      errorMetrics['5xx']++;
    }
    
    if (code?.includes('TOKEN') || code?.includes('AUTH')) {
      errorMetrics['auth_errors']++;
    }
    
    if (code === 'VALIDATION_ERROR') {
      errorMetrics['validation_errors']++;
    }
    
    if (code === 'RATE_LIMIT_EXCEEDED') {
      errorMetrics['rate_limit_errors']++;
    }
  }
  
  // Send metrics to monitoring service periodically
  setInterval(() => {
    monitoringService.sendMetrics(errorMetrics);
  }, 60000); // Every minute
}

For Support: When contacting support about API errors, always include the requestId from the error response. This helps our support team quickly locate and diagnose the issue in our logs.