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:
# 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
# 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:
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');
}
}
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;
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
Common JSON Issues:
// 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
// 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')
});
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
}
}
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
Collect Debug Information:
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);
}
}
Check Service Status:
Verify service availability
Check for maintenance announcements
Review recent changes to your code
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