Appearance
Error Handling Guide
Table of Contents
- Overview
- Error Response Format
- HTTP Status Codes
- Error Categories
- Error Resolution
- Retry Strategies
- Logging and Debugging
- Client Implementation
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
- Consistency: All errors follow the same response format
- Clarity: Error messages are clear and actionable
- Context: Errors include relevant context and debugging information
- Security: Sensitive information is never exposed in error messages
- 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
| Field | Type | Description | Always Present |
|---|---|---|---|
error.code | string | Machine-readable error code | Yes |
error.message | string | Human-readable error message | Yes |
error.target | string | Field or parameter that caused the error | No |
error.details | array | Additional error details | No |
error.innererror | object | Debugging information (dev mode only) | No |
status | integer | HTTP status code | Yes |
timestamp | string | ISO 8601 timestamp | Yes |
path | string | API endpoint path | Yes |
requestId | string | Unique request identifier | Yes |
tenantCode | string | Tenant identifier | When authenticated |
HTTP Status Codes
The API uses standard HTTP status codes grouped into categories:
2xx Success Codes
| Status Code | Meaning | Usage |
|---|---|---|
| 200 OK | Request succeeded | GET, PUT, PATCH success |
| 201 Created | Resource created | POST success |
| 202 Accepted | Request accepted for processing | Async operations |
| 204 No Content | Success with no response body | DELETE success |
3xx Redirection Codes
| Status Code | Meaning | Usage |
|---|---|---|
| 301 Moved Permanently | Resource moved permanently | API version migration |
| 302 Found | Resource temporarily moved | Temporary redirect |
| 304 Not Modified | Resource not modified | Caching validation |
4xx Client Error Codes
| Status Code | Meaning | Common Causes |
|---|---|---|
| 400 Bad Request | Invalid request | Validation errors, malformed JSON |
| 401 Unauthorized | Authentication required | Missing/invalid token |
| 403 Forbidden | Access denied | Insufficient permissions |
| 404 Not Found | Resource not found | Invalid ID, deleted resource |
| 405 Method Not Allowed | Invalid HTTP method | POST to GET-only endpoint |
| 406 Not Acceptable | Cannot produce requested format | Invalid Accept header |
| 409 Conflict | Resource conflict | Duplicate key, version conflict |
| 410 Gone | Resource permanently deleted | Accessing deleted resource |
| 412 Precondition Failed | Precondition not met | If-Match header mismatch |
| 413 Payload Too Large | Request too large | File upload exceeds limit |
| 415 Unsupported Media Type | Invalid content type | Wrong Content-Type header |
| 422 Unprocessable Entity | Business rule violation | Logic/validation errors |
| 429 Too Many Requests | Rate limit exceeded | Too many API calls |
5xx Server Error Codes
| Status Code | Meaning | Action Required |
|---|---|---|
| 500 Internal Server Error | Server error | Retry with exponential backoff |
| 501 Not Implemented | Feature not available | Check API version |
| 502 Bad Gateway | Gateway error | Retry after delay |
| 503 Service Unavailable | Service down/maintenance | Check status page, retry later |
| 504 Gateway Timeout | Request timeout | Retry 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 missingINVALID_FORMAT- Field format is invalidINVALID_TYPE- Wrong data type providedSTRING_TOO_LONG- String exceeds max lengthSTRING_TOO_SHORT- String below min lengthNUMBER_TOO_LARGE- Number exceeds maximumNUMBER_TOO_SMALL- Number below minimumINVALID_ENUM_VALUE- Value not in allowed listINVALID_DATE_RANGE- End date before start dateFUTURE_DATE_REQUIRED- Date must be in futureDUPLICATE_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 providedINVALID_TOKEN- Token is invalid or malformedTOKEN_EXPIRED- Token has expiredMISSING_TENANT_CODE- Tenant code not providedINVALID_TENANT_CODE- Tenant code not recognizedACCOUNT_SUSPENDED- Account has been suspendedACCOUNT_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 permissionsROLE_REQUIRED- Missing required roleSCOPE_REQUIRED- Missing required scopeORGANIZATION_MISMATCH- Wrong organization contextFEATURE_NOT_ENABLED- Feature not enabled for tenantSUBSCRIPTION_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 foundAWARD_NOT_FOUND- Award doesn't existCLASS_NOT_FOUND- Class doesn't existMEMBER_NOT_FOUND- Member doesn't existORGANIZATION_NOT_FOUND- Organization doesn't existENDPOINT_NOT_FOUND- API endpoint doesn't existDELETED_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 endedCAPACITY_EXCEEDED- Class/event is fullDUPLICATE_REGISTRATION- Already registeredPREREQUISITES_NOT_MET- Missing prerequisitesINSUFFICIENT_CREDITS- Not enough creditsAPPROVAL_REQUIRED- Needs manager approvalBUDGET_EXCEEDED- Over budget limitSCHEDULING_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)
- Check the
detailsarray for specific field errors - Validate input against schema documentation
- Ensure required fields are present
- Verify data types and formats
- 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)
- Verify tenant code is included in headers
- Check token expiration
- Refresh token if expired
- 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)
- Read the
Retry-Afterheader - Implement exponential backoff
- Queue requests if necessary
- 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.
