inChurch
Getting Started

Error Handling

Understanding and handling errors effectively is crucial for building robust integrations with the InChurch API. This guide covers error response formats, common error scenarios, and best practices for error handling.

Error Response Format

The InChurch API uses standard HTTP status codes and returns detailed error information in a consistent JSON format:

JSONCode
{ "error": { "code": "VALIDATION_ERROR", "message": "The given data was invalid.", "details": { "email": ["The email field is required."], "phone": ["The phone field must be a valid phone number."] } } }

Error Object Structure

FieldTypeDescription
codestringMachine-readable error code for programmatic handling
messagestringHuman-readable error description
detailsobjectAdditional error information (optional)

HTTP Status Codes

4xx Client Errors

Status CodeError CodeDescription
400VALIDATION_ERRORRequest data validation failed
400INVALID_REQUESTRequest format or structure is invalid
401UNAUTHORIZEDMissing or invalid authentication credentials
403FORBIDDENValid credentials but insufficient permissions
404NOT_FOUNDRequested resource does not exist
409CONFLICTResource conflict (duplicate data, etc.)
422UNPROCESSABLE_ENTITYValid request but business logic error
429RATE_LIMITEDToo many requests (rate limit exceeded)

5xx Server Errors

Status CodeError CodeDescription
500INTERNAL_ERRORUnexpected server error
502BAD_GATEWAYGateway or proxy error
503SERVICE_UNAVAILABLEService temporarily unavailable
504GATEWAY_TIMEOUTRequest timeout

Common Error Scenarios

Authentication Errors

Invalid API Credentials

Code
HTTP/1.1 401 Unauthorized
JSONCode
{ "error": { "code": "UNAUTHORIZED", "message": "Invalid API credentials", "details": { "reason": "API key not found or API secret mismatch" } } }

Insufficient Permissions

Code
HTTP/1.1 403 Forbidden
JSONCode
{ "error": { "code": "FORBIDDEN", "message": "Insufficient permissions for this resource", "details": { "required_scope": "people:write", "current_scopes": ["people:read", "donations:read"] } } }

Validation Errors

Missing Required Fields

Code
HTTP/1.1 400 Bad Request
JSONCode
{ "error": { "code": "VALIDATION_ERROR", "message": "The given data was invalid.", "details": { "full_name": ["The full name field is required."], "email": ["The email field is required."] } } }

Invalid Data Format

Code
HTTP/1.1 400 Bad Request
JSONCode
{ "error": { "code": "VALIDATION_ERROR", "message": "The given data was invalid.", "details": { "email": ["The email must be a valid email address."], "birthday": ["The birthday must be a valid date in YYYY-MM-DD format."], "mobile_phone": ["The mobile phone must be a valid phone number."] } } }

Rate Limiting

Code
HTTP/1.1 429 Too Many Requests Retry-After: 60
JSONCode
{ "error": { "code": "RATE_LIMITED", "message": "Too many requests. Please retry after 60 seconds.", "details": { "limit": 200, "window": "1 minute", "reset_at": "2023-12-15T15:21:00Z" } } }

Resource Not Found

Code
HTTP/1.1 404 Not Found
JSONCode
{ "error": { "code": "NOT_FOUND", "message": "Resource not found", "details": { "resource_type": "person", "resource_id": 99999 } } }

Error Handling Best Practices

1. Check Status Codes First

Always check the HTTP status code before processing the response:

JavascriptCode
async function handleApiResponse(response) { if (!response.ok) { const errorData = await response.json(); switch (response.status) { case 400: throw new ValidationError(errorData.error); case 401: throw new AuthenticationError(errorData.error); case 403: throw new PermissionError(errorData.error); case 404: throw new NotFoundError(errorData.error); case 429: throw new RateLimitError(errorData.error, response.headers.get('Retry-After')); case 500: throw new ServerError(errorData.error); default: throw new ApiError(errorData.error, response.status); } } return response.json(); }
Code
import requests from datetime import datetime, timedelta class ApiError(Exception): def __init__(self, error_data, status_code): self.code = error_data.get('code') self.message = error_data.get('message') self.details = error_data.get('details', {}) self.status_code = status_code super().__init__(self.message) def handle_api_response(response): if not response.ok: try: error_data = response.json().get('error', {}) except ValueError: error_data = {'message': 'Unknown error'} if response.status_code == 400: raise ValidationError(error_data, response.status_code) elif response.status_code == 401: raise AuthenticationError(error_data, response.status_code) elif response.status_code == 403: raise PermissionError(error_data, response.status_code) elif response.status_code == 404: raise NotFoundError(error_data, response.status_code) elif response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) raise RateLimitError(error_data, response.status_code, retry_after) else: raise ApiError(error_data, response.status_code) return response.json()

2. Implement Custom Error Classes

Create specific error classes for different types of errors:

JavascriptCode
class ApiError extends Error { constructor(errorData, statusCode) { super(errorData.message); this.name = 'ApiError'; this.code = errorData.code; this.details = errorData.details; this.statusCode = statusCode; } } class ValidationError extends ApiError { constructor(errorData) { super(errorData, 400); this.name = 'ValidationError'; this.validationErrors = errorData.details || {}; } getFieldErrors(field) { return this.validationErrors[field] || []; } } class AuthenticationError extends ApiError { constructor(errorData) { super(errorData, 401); this.name = 'AuthenticationError'; } } class RateLimitError extends ApiError { constructor(errorData, retryAfter) { super(errorData, 429); this.name = 'RateLimitError'; this.retryAfter = parseInt(retryAfter); this.retryAt = new Date(Date.now() + this.retryAfter * 1000); } }
Code
class ValidationError(ApiError): def __init__(self, error_data, status_code): super().__init__(error_data, status_code) self.validation_errors = error_data.get('details', {}) def get_field_errors(self, field): return self.validation_errors.get(field, []) class AuthenticationError(ApiError): pass class PermissionError(ApiError): pass class NotFoundError(ApiError): pass class RateLimitError(ApiError): def __init__(self, error_data, status_code, retry_after=60): super().__init__(error_data, status_code) self.retry_after = retry_after self.retry_at = datetime.now() + timedelta(seconds=retry_after)

3. Implement Retry Logic

Handle transient errors with appropriate retry strategies:

JavascriptCode
async function apiRequestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const response = await fetch(url, options); return await handleApiResponse(response); } catch (error) { // Don't retry on client errors (4xx except 429) if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) { throw error; } // On last attempt, throw the error if (attempt === maxRetries) { throw error; } // Calculate delay let delay = Math.pow(2, attempt) * 1000; // Exponential backoff if (error instanceof RateLimitError) { delay = error.retryAfter * 1000; } console.log(`Request failed, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, delay)); } } }
Code
import time import random def api_request_with_retry(url, max_retries=3, **kwargs): for attempt in range(max_retries + 1): try: response = requests.get(url, **kwargs) return handle_api_response(response) except ApiError as error: # Don't retry client errors (except rate limiting) if 400 <= error.status_code < 500 and error.status_code != 429: raise error # On last attempt, raise the error if attempt == max_retries: raise error # Calculate delay if isinstance(error, RateLimitError): delay = error.retry_after else: delay = (2 ** attempt) + random.uniform(0, 1) print(f"Request failed, retrying in {delay}s (attempt {attempt + 1}/{max_retries})") time.sleep(delay)

4. Log Errors Appropriately

Log errors with appropriate detail levels:

JavascriptCode
function logApiError(error, context = {}) { const logData = { timestamp: new Date().toISOString(), error_type: error.name, error_code: error.code, message: error.message, status_code: error.statusCode, context: context }; // Log validation errors as warnings if (error instanceof ValidationError) { console.warn('Validation error:', logData); } // Log authentication/permission errors as warnings else if (error instanceof AuthenticationError || error instanceof PermissionError) { console.warn('Access error:', logData); } // Log server errors as errors else if (error.statusCode >= 500) { console.error('Server error:', logData); } // Log other errors as info else { console.info('API error:', logData); } }

5. User-Friendly Error Messages

Convert technical errors to user-friendly messages:

JavascriptCode
function getErrorMessage(error) { const errorMessages = { 'VALIDATION_ERROR': 'Please check your input and try again.', 'UNAUTHORIZED': 'Authentication failed. Please check your credentials.', 'FORBIDDEN': 'You don\'t have permission to perform this action.', 'NOT_FOUND': 'The requested resource was not found.', 'RATE_LIMITED': 'Too many requests. Please wait a moment and try again.', 'INTERNAL_ERROR': 'Something went wrong on our end. Please try again later.' }; return errorMessages[error.code] || 'An unexpected error occurred.'; } // Usage in UI try { const result = await createPerson(personData); showSuccess('Person created successfully!'); } catch (error) { if (error instanceof ValidationError) { showValidationErrors(error.validationErrors); } else { showError(getErrorMessage(error)); } }

Error Monitoring & Alerting

Track Error Patterns

Monitor error rates and patterns to identify issues:

JavascriptCode
class ErrorTracker { constructor() { this.errors = []; } track(error, context = {}) { this.errors.push({ timestamp: new Date(), code: error.code, statusCode: error.statusCode, message: error.message, context }); // Keep only last 100 errors if (this.errors.length > 100) { this.errors.shift(); } this.checkErrorRate(); } checkErrorRate() { const lastHour = Date.now() - (60 * 60 * 1000); const recentErrors = this.errors.filter(e => e.timestamp > lastHour); if (recentErrors.length > 50) { console.warn('High error rate detected:', recentErrors.length, 'errors in last hour'); } } getErrorStats() { const stats = {}; this.errors.forEach(error => { const key = `${error.statusCode}_${error.code}`; stats[key] = (stats[key] || 0) + 1; }); return stats; } }
Last modified on