ESFA Backend API Documentation Help

Troubleshooting Guide

This guide helps you diagnose and resolve common issues when integrating with the ESFA Backend API. It covers authentication problems, request failures, data validation issues, and performance concerns.

Quick Diagnostic Checklist

Before diving into specific issues, run through this quick checklist:

  • API Credentials: Verify your apiKey and secretKey are correct

  • Network Connectivity: Confirm you can reach both Authentication and Backend services

  • Token Validity: Check if your Bearer token is current and not expired

  • Request Format: Ensure proper JSON formatting and required headers

  • Endpoint URLs: Verify you're using correct URLs for your environment

  • HTTP Method: Confirm you're using the right HTTP method (GET/POST)

Authentication Issues

Problem: "Invalid or missing authentication token" (401)

Symptoms:

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

Common Causes & Solutions:

Missing Authorization Header

# Wrong - no Authorization header curl https://api.your-service.tld/api/v1/games/TTT/matches/123 # Correct - include Bearer token curl -H "Authorization: Bearer eyJhbGci..." \ https://api.your-service.tld/api/v1/games/TTT/matches/123

Malformed Authorization Header

# Wrong - missing "Bearer " prefix curl -H "Authorization: eyJhbGci..." \ https://api.your-service.tld/api/v1/games/TTT/matches/123 # Wrong - extra spaces curl -H "Authorization: Bearer eyJhbGci..." \ https://api.your-service.tld/api/v1/games/TTT/matches/123 # Correct - exact format curl -H "Authorization: Bearer eyJhbGci..." \ https://api.your-service.tld/api/v1/games/TTT/matches/123

Token Expired

// Check token expiration before using function isTokenExpired(tokenData) { const expiresAt = new Date(tokenData.expires); const now = new Date(); return now >= expiresAt; } // Auto-refresh expired tokens async function getValidToken() { if (!currentToken || isTokenExpired(currentToken)) { currentToken = await refreshToken(); } return currentToken.token; }

Token Corruption

// Validate JWT structure function validateJWTFormat(token) { const parts = token.split('.'); if (parts.length !== 3) { throw new Error('Invalid JWT format - must have 3 parts'); } // Check if parts are valid base64 try { parts.forEach(part => { atob(part.replace(/-/g, '+').replace(/_/g, '/')); }); } catch (error) { throw new Error('Invalid JWT format - invalid base64 encoding'); } }

Problem: "Invalid API key or secret key" (401 from Auth Service)

Symptoms:

{ "errors": [ { "i18N": "INVALID_CREDENTIALS", "error": "Invalid API key or secret key", "type": "Unauthorized" } ] }

Debugging Steps:

  1. Verify Credentials Format:

// Check for common issues function validateCredentials(apiKey, secretKey) { if (!apiKey || !secretKey) { throw new Error('API key and secret key are required'); } if (typeof apiKey !== 'string' || typeof secretKey !== 'string') { throw new Error('Credentials must be strings'); } if (apiKey.includes(' ') || secretKey.includes(' ')) { console.warn('Warning: Credentials contain spaces'); } if (apiKey.length < 10 || secretKey.length < 10) { console.warn('Warning: Credentials seem unusually short'); } }
  1. Environment-Specific URLs:

// Ensure you're using the right Auth Service URL const AUTH_URLS = { development: 'https://auth-dev.your-service.tld', staging: 'https://auth-staging.your-service.tld', production: 'https://auth.your-service.tld' }; const authUrl = AUTH_URLS[process.env.NODE_ENV] || AUTH_URLS.development;
  1. Test with cURL:

# Test authentication directly curl -X POST https://auth.your-service.tld/v1/oauth/token/machine \ -H "Content-Type: application/json" \ -d '{ "apiKey": "your-actual-api-key", "secretKey": "your-actual-secret-key" }' \ -v

Request Format Issues

Problem: "Bad request - invalid input" (400)

Common JSON Issues:

Missing Content-Type Header

// Wrong - missing Content-Type fetch('/api/v1/games/TTT/matches/123/finish', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: JSON.stringify(data) }); // Correct - include Content-Type fetch('/api/v1/games/TTT/matches/123/finish', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify(data) });

Invalid JSON Syntax

// Debug JSON serialization function safeJsonStringify(data) { try { const json = JSON.stringify(data); // Validate by parsing back JSON.parse(json); return json; } catch (error) { console.error('JSON serialization error:', error); console.error('Data that failed:', data); throw new Error('Invalid JSON data'); } }

Character Encoding Issues

// Ensure UTF-8 encoding const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(data) };

Problem: Match Results Validation Errors

Invalid Results Data:

Missing Required Fields

// Validation function function validateMatchResults(results) { const errors = []; if (!Array.isArray(results)) { errors.push('Results must be an array'); return errors; } if (results.length === 0) { errors.push('Results array cannot be empty'); return errors; } results.forEach((result, index) => { if (!result.hasOwnProperty('playerId')) { errors.push(`Result ${index}: missing playerId`); } if (!result.hasOwnProperty('place')) { errors.push(`Result ${index}: missing place`); } if (!result.hasOwnProperty('score')) { errors.push(`Result ${index}: missing score`); } }); return errors; }

Data Type Issues

// Type validation and conversion function normalizeResults(results) { return results.map((result, index) => { const normalized = { ...result }; // Ensure playerId is string if (typeof normalized.playerId !== 'string') { normalized.playerId = String(normalized.playerId); } // Ensure place is integer if (typeof normalized.place !== 'number') { const place = parseInt(normalized.place, 10); if (isNaN(place)) { throw new Error(`Result ${index}: place must be numeric`); } normalized.place = place; } // Ensure score is number if (typeof normalized.score !== 'number') { const score = parseFloat(normalized.score); if (isNaN(score)) { throw new Error(`Result ${index}: score must be numeric`); } normalized.score = score; } return normalized; }); }

Place Sequence Validation

// Comprehensive place validation function validatePlaceSequence(results) { const places = results.map(r => r.place).sort((a, b) => a - b); // Check for duplicates const uniquePlaces = new Set(places); if (uniquePlaces.size !== places.length) { const duplicates = places.filter((place, index) => places.indexOf(place) !== index); throw new Error(`Duplicate places found: ${duplicates.join(', ')}`); } // Check consecutive sequence for (let i = 0; i < places.length; i++) { if (places[i] !== i + 1) { throw new Error(`Places must be consecutive starting from 1. Missing: ${i + 1}`); } } // Check minimum value if (places[0] !== 1) { throw new Error('Places must start from 1'); } }

Network and Connectivity Issues

Problem: Connection Timeouts

Debugging Network Issues:

Check Service Availability

# Test connectivity to services curl -I https://auth.your-service.tld/health curl -I https://api.your-service.tld/health # Test DNS resolution nslookup auth.your-service.tld nslookup api.your-service.tld # Test network latency ping auth.your-service.tld ping api.your-service.tld

Configure Appropriate Timeouts

// Set reasonable timeouts const timeoutConfig = { connect: 10000, // 10s to establish connection read: 30000, // 30s to read response retry: 3 // retry attempts }; async function makeRequestWithTimeout(url, options) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutConfig.read); try { const response = await fetch(url, { ...options, signal: controller.signal }); return response; } finally { clearTimeout(timeoutId); } }

Implement Connection Pooling

// Node.js HTTP agent configuration const https = require('https'); const agent = new https.Agent({ keepAlive: true, maxSockets: 50, maxFreeSockets: 10, timeout: 30000 }); // Use agent in requests const response = await fetch(url, { agent: agent, // ... other options });

Problem: SSL/TLS Certificate Issues

Certificate Validation:

# Check SSL certificate openssl s_client -connect auth.your-service.tld:443 -servername auth.your-service.tld # Verify certificate chain curl -vvI https://auth.your-service.tld/

Handle Certificate Issues in Code:

// For development only - never use in production if (process.env.NODE_ENV === 'development') { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } // Better approach: specify trusted CA const https = require('https'); const fs = require('fs'); const agent = new https.Agent({ ca: fs.readFileSync('path/to/ca-cert.pem') });

Performance Issues

Problem: Slow API Response Times

Performance Monitoring:

// Measure request performance async function measureApiCall(apiCall) { const start = Date.now(); try { const result = await apiCall(); const duration = Date.now() - start; console.log(`API call completed in ${duration}ms`); if (duration > 5000) { console.warn('Slow API response detected'); } return result; } catch (error) { const duration = Date.now() - start; console.error(`API call failed after ${duration}ms:`, error); throw error; } }

Connection Optimization:

// HTTP/2 and connection reuse const http2 = require('http2'); class OptimizedApiClient { constructor(baseUrl) { this.session = http2.connect(baseUrl); this.session.on('error', (err) => console.error(err)); } async request(path, options = {}) { const req = this.session.request({ ':method': options.method || 'GET', ':path': path, 'authorization': `Bearer ${options.token}`, 'content-type': 'application/json' }); return new Promise((resolve, reject) => { let data = ''; req.on('data', chunk => data += chunk); req.on('end', () => { try { resolve(JSON.parse(data)); } catch (err) { reject(err); } }); req.on('error', reject); if (options.body) { req.write(JSON.stringify(options.body)); } req.end(); }); } close() { this.session.close(); } }

Problem: Rate Limiting (if applicable)

Implement Rate Limiting Protection:

class RateLimiter { constructor(maxRequests = 100, windowMs = 60000) { this.maxRequests = maxRequests; this.windowMs = windowMs; this.requests = []; } async wait() { const now = Date.now(); // Remove old requests outside window this.requests = this.requests.filter(time => now - time < this.windowMs); // Check if we're at limit if (this.requests.length >= this.maxRequests) { const oldestRequest = Math.min(...this.requests); const waitTime = this.windowMs - (now - oldestRequest); console.log(`Rate limit reached, waiting ${waitTime}ms`); await new Promise(resolve => setTimeout(resolve, waitTime)); return this.wait(); // Recursive check } // Add current request this.requests.push(now); } } // Usage const rateLimiter = new RateLimiter(50, 60000); // 50 requests per minute async function rateLimitedApiCall(apiCall) { await rateLimiter.wait(); return await apiCall(); }

Data and State Issues

Problem: "Match not found" (404)

Debugging Match Existence:

// Comprehensive match debugging async function debugMatchIssue(gameCode, matchId, token) { console.log('Debugging match issue:'); console.log('- Game Code:', gameCode); console.log('- Match ID:', matchId); console.log('- Token (first 20 chars):', token.substring(0, 20) + '...'); // Validate inputs if (!gameCode || !matchId || !token) { console.error('Missing required parameters'); return; } // Check UUID format for match ID const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(matchId)) { console.error('Match ID does not appear to be valid UUID format'); } // Test with different variations const testCases = [ { gameCode, matchId }, { gameCode: gameCode.toUpperCase(), matchId }, { gameCode: gameCode.toLowerCase(), matchId } ]; for (const testCase of testCases) { try { const response = await fetch(`/api/v1/games/${testCase.gameCode}/matches/${testCase.matchId}`, { headers: { 'Authorization': `Bearer ${token}` } }); console.log(`Test ${testCase.gameCode}/${testCase.matchId}: ${response.status}`); if (response.ok) { console.log('✓ Found match with this variation'); return; } } catch (error) { console.error(`Test failed:`, error.message); } } }

Problem: "Match has expired" (410)

Handle Expired Matches:

// Graceful expiration handling async function handleMatchOperation(operation) { try { return await operation(); } catch (error) { if (error.response?.status === 410) { const errorData = await error.response.json(); if (errorData.errors[0]?.i18N === 'MATCH_EXPIRED') { // Handle expired match gracefully console.log('Match has expired - cleaning up local state'); await cleanupExpiredMatch(); // Show user-friendly message throw new UserFriendlyError('This match has expired. Please start a new game.'); } } throw error; // Re-throw other errors } }

Debugging Tools

API Response Inspection

// Comprehensive response logging async function logApiResponse(response) { console.log('=== API Response Debug ==='); console.log('Status:', response.status, response.statusText); console.log('Headers:', Object.fromEntries(response.headers.entries())); const responseText = await response.text(); console.log('Body Length:', responseText.length); try { const responseJson = JSON.parse(responseText); console.log('Parsed JSON:', JSON.stringify(responseJson, null, 2)); } catch (error) { console.log('Raw Body (not JSON):', responseText); } console.log('=== End Debug ==='); }

Request Debugging

// Debug outgoing requests function debugRequest(url, options) { console.log('=== API Request Debug ==='); console.log('URL:', url); console.log('Method:', options.method || 'GET'); // Log headers (redact authorization) const headers = { ...options.headers }; if (headers.Authorization) { headers.Authorization = headers.Authorization.substring(0, 20) + '...'; } console.log('Headers:', headers); if (options.body) { try { const bodyObj = JSON.parse(options.body); console.log('Body:', JSON.stringify(bodyObj, null, 2)); } catch (error) { console.log('Body (raw):', options.body); } } console.log('=== End Request Debug ==='); }

Environment Validation

// Validate development environment function validateEnvironment() { const required = [ 'API_KEY', 'SECRET_KEY', 'AUTH_SERVICE_URL', 'API_SERVICE_URL' ]; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { console.error('Missing environment variables:', missing); process.exit(1); } // Validate URLs const urls = [ process.env.AUTH_SERVICE_URL, process.env.API_SERVICE_URL ]; urls.forEach(url => { try { new URL(url); } catch (error) { console.error('Invalid URL:', url); process.exit(1); } }); console.log('✓ Environment validation passed'); }

Getting Help

Before Contacting Support

  1. Collect Debug Information:

    • API endpoint and method used

    • Complete error response

    • Request payload (remove sensitive data)

    • Timestamp of the issue

    • Environment details

  2. Test with Minimal Example:

    // Minimal reproduction case async function minimalTest() { try { // 1. Get token const tokenResponse = await getToken(); console.log('✓ Token obtained'); // 2. Test simple request const matchResponse = await getMatch('TTT', 'test-match-id', tokenResponse.token); console.log('✓ API request successful'); } catch (error) { console.error('✗ Error in minimal test:', error); } }
  3. Check Service Status:

    • Verify service availability

    • Check for maintenance announcements

    • Review recent changes to your code

Information to Include in Support Requests

  • Error Details: Complete error response with status codes

  • Request Details: Endpoint, method, headers (redacted), payload

  • Environment: Development/staging/production, library versions

  • Reproduction Steps: Minimal code to reproduce the issue

  • Frequency: Is this a one-time issue or recurring?

  • Impact: How many users/requests are affected?

05 September 2025