ESFA Backend API Documentation Help

Error Handling

This guide provides comprehensive information about error handling in the ESFA Backend API, including error response formats, status codes, common scenarios, and best practices for robust integration.

Error Response Format

All ESFA Backend API endpoints use a consistent error response format called the ErrorEnvelope. This standardized structure makes it easier to handle errors programmatically.

Standard Error Structure

{ "errors": [ { "i18N": "MATCH_NOT_FOUND", "error": "Match not found", "type": "NotFound" } ] }

Fields Description

Field

Type

Description

errors

array

Array of error objects (may contain multiple errors)

errors[].i18N

string

Internationalization key for programmatic handling

errors[].error

string

Human-readable error message

errors[].type

string

Error type classification

HTTP Status Codes

The API uses standard HTTP status codes to indicate the type of error:

Client Errors (4xx)

400 Bad Request

Invalid request data or malformed payload.

Common causes:

  • Missing required fields

  • Invalid JSON format

  • Data validation failures

  • Business logic violations

Example:

{ "errors": [ { "i18N": "INVALID_RESULTS", "error": "Invalid match results data", "type": "BadRequest" } ] }

Handling:

  • Validate request data before sending

  • Check API documentation for required fields

  • Ensure proper JSON formatting

401 Unauthorized

Authentication required or token invalid.

Common causes:

  • Missing Authorization header

  • Invalid Bearer token

  • Expired token

  • Malformed token

Example:

{ "errors": [ { "i18N": "UNAUTHORIZED", "error": "Invalid or missing authentication token", "type": "Unauthorized" } ] }

Handling:

  • Obtain fresh token from Authentication Service

  • Verify Authorization header format: Bearer <token>

  • Implement automatic token refresh

403 Forbidden

Valid authentication but insufficient permissions.

Common causes:

  • Token lacks required permissions

  • Resource access restrictions

  • Account limitations

Example:

{ "errors": [ { "i18N": "FORBIDDEN", "error": "Access denied", "type": "Forbidden" } ] }

Handling:

  • Check account permissions

  • Contact administrator for access

  • Verify correct API credentials

404 Not Found

Requested resource does not exist.

Common causes:

  • Invalid match ID

  • Invalid game code

  • Deleted resources

  • Incorrect endpoint URL

Example:

{ "errors": [ { "i18N": "MATCH_NOT_FOUND", "error": "Match not found", "type": "NotFound" } ] }

Handling:

  • Verify resource identifiers

  • Check if resource was deleted

  • Confirm correct endpoint URL

409 Conflict

Resource state conflict.

Common causes:

  • Match already finished

  • Concurrent modifications

  • Invalid state transitions

Example:

{ "errors": [ { "i18N": "MATCH_ALREADY_FINISHED", "error": "Match has already been finished", "type": "Conflict" } ] }

Handling:

  • Check current resource state

  • Implement optimistic concurrency control

  • Retry with updated state

410 Gone

Resource expired or permanently deleted.

Common causes:

  • Match exceeded TTL

  • Resource archived

  • System cleanup

Example:

{ "errors": [ { "i18N": "MATCH_EXPIRED", "error": "Match has expired", "type": "Gone" } ] }

Handling:

  • Handle gracefully as permanent condition

  • Don't retry the same request

  • Update local cache/state

Server Errors (5xx)

500 Internal Server Error

Temporary server-side issue.

Common causes:

  • Database connectivity issues

  • Service dependencies down

  • Internal processing errors

  • Resource exhaustion

Example:

{ "errors": [ { "i18N": "SERVER_ERROR", "error": "Internal server error", "type": "ServerError" } ] }

Handling:

  • Implement retry with exponential backoff

  • Log for debugging

  • Show user-friendly error message

Common Error Scenarios

Authentication Errors

Token Expired

// Detect expired token if (response.status === 401) { const errorData = await response.json(); if (errorData.errors[0]?.i18N === 'UNAUTHORIZED') { // Token likely expired, refresh it await refreshToken(); // Retry original request return retryRequest(originalRequest); } }

Invalid Credentials

// Handle authentication service errors async function authenticateWithRetry(credentials, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const token = await getToken(credentials); return token; } catch (error) { if (error.response?.status === 401) { throw new Error('Invalid credentials - check API key and secret'); } if (attempt === maxRetries) { throw error; } // Exponential backoff for server errors await sleep(Math.pow(2, attempt) * 1000); } } }

Match Operation Errors

Match Not Found

async function getMatchSafely(gameCode, matchId, token) { try { const match = await getMatch(gameCode, matchId, token); return { success: true, data: match }; } catch (error) { if (error.response?.status === 404) { return { success: false, error: 'MATCH_NOT_FOUND', message: 'The requested match could not be found' }; } throw error; // Re-throw unexpected errors } }

Match Already Finished

async function finishMatchSafely(gameCode, matchId, results, token) { try { await finishMatch(gameCode, matchId, results, token); return { success: true }; } catch (error) { if (error.response?.status === 409) { const errorData = await error.response.json(); if (errorData.errors[0]?.i18N === 'MATCH_ALREADY_FINISHED') { return { success: false, error: 'ALREADY_FINISHED', message: 'Match has already been completed' }; } } throw error; } }

Validation Errors

Invalid Match Results

function validateAndFormatResults(results) { const errors = []; if (!Array.isArray(results) || results.length === 0) { errors.push('Results must be a non-empty array'); } const places = new Set(); const playerIds = new Set(); results.forEach((result, index) => { // Check required fields if (!result.playerId) { errors.push(`Result ${index}: playerId is required`); } if (typeof result.place !== 'number' || result.place < 1) { errors.push(`Result ${index}: place must be positive number`); } if (typeof result.score !== 'number') { errors.push(`Result ${index}: score must be numeric`); } // Check for duplicates if (places.has(result.place)) { errors.push(`Duplicate place ${result.place} found`); } if (playerIds.has(result.playerId)) { errors.push(`Duplicate playerId ${result.playerId} found`); } places.add(result.place); playerIds.add(result.playerId); }); // Check consecutive places const sortedPlaces = Array.from(places).sort((a, b) => a - b); for (let i = 0; i < sortedPlaces.length; i++) { if (sortedPlaces[i] !== i + 1) { errors.push(`Missing place ${i + 1}. Places must be consecutive`); break; } } if (errors.length > 0) { throw new ValidationError(errors); } return results; }

Best Practices

Error Handling Strategy

1. Categorize Errors

class APIErrorHandler { static categorizeError(error) { const status = error.response?.status; if (status >= 400 && status < 500) { return { category: 'CLIENT_ERROR', retryable: status === 401, // Only retry auth errors userMessage: this.getUserMessage(error) }; } if (status >= 500) { return { category: 'SERVER_ERROR', retryable: true, userMessage: 'Service temporarily unavailable. Please try again.' }; } return { category: 'UNKNOWN', retryable: false, userMessage: 'An unexpected error occurred.' }; } }

2. Implement Retry Logic

async function retryableRequest(requestFn, options = {}) { const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000, retryCondition = (error) => error.response?.status >= 500 } = options; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await requestFn(); } catch (error) { // Don't retry if not retryable if (!retryCondition(error) || attempt === maxRetries) { throw error; } // Exponential backoff with jitter const delay = Math.min( baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000, maxDelay ); console.log(`Request failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`); await sleep(delay); } } }

3. Circuit Breaker Pattern

class CircuitBreaker { constructor(options = {}) { this.failureThreshold = options.failureThreshold || 5; this.resetTimeout = options.resetTimeout || 30000; this.monitoringPeriod = options.monitoringPeriod || 10000; this.failureCount = 0; this.lastFailureTime = null; this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN } async execute(operation) { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime > this.resetTimeout) { this.state = 'HALF_OPEN'; } else { throw new Error('Circuit breaker is OPEN'); } } try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failureCount = 0; this.state = 'CLOSED'; } onFailure() { this.failureCount++; this.lastFailureTime = Date.now(); if (this.failureCount >= this.failureThreshold) { this.state = 'OPEN'; } } }

User Experience

1. User-Friendly Error Messages

const ERROR_MESSAGES = { MATCH_NOT_FOUND: 'The game match you\'re looking for doesn\'t exist or has been removed.', MATCH_EXPIRED: 'This match has expired. Please start a new game.', MATCH_ALREADY_FINISHED: 'This match has already been completed.', UNAUTHORIZED: 'Your session has expired. Please refresh the page and try again.', SERVER_ERROR: 'We\'re experiencing technical difficulties. Please try again in a few moments.', NETWORK_ERROR: 'Unable to connect to the server. Please check your internet connection.' }; function getUserFriendlyMessage(error) { const i18nKey = error.response?.data?.errors?.[0]?.i18N; return ERROR_MESSAGES[i18nKey] || ERROR_MESSAGES.SERVER_ERROR; }

2. Progress Indication

class APIClient { async finishMatchWithProgress(gameCode, matchId, results, onProgress) { try { onProgress({ status: 'validating', message: 'Validating results...' }); validateResults(results); onProgress({ status: 'submitting', message: 'Submitting results...' }); const response = await this.finishMatch(gameCode, matchId, results); onProgress({ status: 'complete', message: 'Match completed successfully!' }); return response; } catch (error) { const message = getUserFriendlyMessage(error); onProgress({ status: 'error', message }); throw error; } } }

Logging and Monitoring

1. Structured Error Logging

function logError(error, context = {}) { const logEntry = { timestamp: new Date().toISOString(), level: 'ERROR', message: error.message, stack: error.stack, context, api: { method: context.method, url: context.url, status: error.response?.status, response: error.response?.data }, user: { id: context.userId, session: context.sessionId } }; // Remove sensitive data if (logEntry.context.headers) { delete logEntry.context.headers.Authorization; } console.error(JSON.stringify(logEntry)); // Send to monitoring service if (typeof sendToMonitoring === 'function') { sendToMonitoring(logEntry); } }

2. Error Metrics

class ErrorMetrics { static increment(errorType, endpoint) { // Increment error counter if (typeof metrics !== 'undefined') { metrics.increment('api.errors', { type: errorType, endpoint: endpoint }); } } static recordLatency(duration, success) { if (typeof metrics !== 'undefined') { metrics.histogram('api.request_duration', duration, { success: success.toString() }); } } }

Error Reference

Complete Error Code Reference

i18N Key

HTTP Status

Description

Retry?

User Action

UNAUTHORIZED

401

Invalid/missing token

Yes*

Refresh session

FORBIDDEN

403

Access denied

No

Check permissions

MATCH_NOT_FOUND

404

Match doesn't exist

No

Verify match ID

MATCH_EXPIRED

410

Match has expired

No

Start new match

MATCH_ALREADY_FINISHED

409

Match completed

No

View results

INVALID_RESULTS

400

Invalid match data

No

Fix data format

INVALID_REQUEST

400

Malformed request

No

Check request format

INVALID_CREDENTIALS

401

Wrong API keys

No

Update credentials

SERVER_ERROR

500

Internal error

Yes

Wait and retry

*Only after obtaining fresh token

Integration Checklist

Error Response Parsing

  • Parse ErrorEnvelope format correctly

  • Handle multiple errors in response

  • Extract i18N keys for programmatic handling

HTTP Status Code Handling

  • Implement specific logic for each status code

  • Distinguish between client and server errors

  • Handle edge cases (network timeouts, etc.)

Retry Strategy

  • Implement exponential backoff

  • Set maximum retry limits

  • Only retry appropriate error types

User Experience

  • Show user-friendly error messages

  • Provide actionable error guidance

  • Implement loading states and progress indicators

Monitoring and Logging

  • Log errors with sufficient context

  • Track error rates and patterns

  • Set up alerts for critical errors

05 September 2025