ESFA Backend API Documentation Help

Code Examples

This document provides comprehensive code examples for integrating with the ESFA Backend API across different programming languages and frameworks. Use these examples as starting points for your implementation.

JavaScript/TypeScript

Basic API Client

interface TokenData { token: string; expires: string; } interface Player { id: string; name: string; status: string; imgAvatarUrl?: string; } interface GameMatch { matchId: string; gameCode: string; players: Player[]; } interface MatchResult { playerId: string; place: number; score: number; } class ESFAApiClient { private baseUrl: string; private authUrl: string; private apiKey: string; private secretKey: string; private currentToken: TokenData | null = null; constructor(config: { baseUrl: string; authUrl: string; apiKey: string; secretKey: string; }) { this.baseUrl = config.baseUrl; this.authUrl = config.authUrl; this.apiKey = config.apiKey; this.secretKey = config.secretKey; } private async getValidToken(): Promise<string> { if (!this.currentToken || this.isTokenExpired(this.currentToken)) { this.currentToken = await this.authenticate(); } return this.currentToken.token; } private isTokenExpired(tokenData: TokenData): boolean { const expiresAt = new Date(tokenData.expires); const now = new Date(); // Add 5-minute buffer return now >= new Date(expiresAt.getTime() - 5 * 60 * 1000); } private async authenticate(): Promise<TokenData> { const response = await fetch(`${this.authUrl}/v1/oauth/token/machine`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ apiKey: this.apiKey, secretKey: this.secretKey, }), }); if (!response.ok) { throw new Error(`Authentication failed: ${response.status}`); } const data = await response.json(); return data.data; } private async makeRequest<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const token = await this.getValidToken(); const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...options.headers, }, }); if (response.status === 401) { // Token might be expired, refresh and retry once this.currentToken = await this.authenticate(); const retryToken = this.currentToken.token; const retryResponse = await fetch(url, { ...options, headers: { 'Authorization': `Bearer ${retryToken}`, 'Content-Type': 'application/json', ...options.headers, }, }); if (!retryResponse.ok) { throw new Error(`API request failed: ${retryResponse.status}`); } if (retryResponse.status === 204) { return null as T; } return await retryResponse.json(); } if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(`API request failed: ${response.status} - ${errorData.errors?.[0]?.error || response.statusText}`); } if (response.status === 204) { return null as T; } return await response.json(); } async getMatch(gameCode: string, matchId: string): Promise<GameMatch> { return this.makeRequest<GameMatch>(`/api/v1/games/${gameCode}/matches/${matchId}`); } async finishMatch( gameCode: string, matchId: string, results: MatchResult[] ): Promise<void> { await this.makeRequest<void>( `/api/v1/games/${gameCode}/matches/${matchId}/finish`, { method: 'POST', body: JSON.stringify({ results }), } ); } }

Usage Example

// Initialize client const client = new ESFAApiClient({ baseUrl: 'https://api.your-service.tld', authUrl: 'https://auth.your-service.tld', apiKey: process.env.ESFA_API_KEY!, secretKey: process.env.ESFA_SECRET_KEY!, }); // Complete workflow example async function completeMatchWorkflow() { try { // 1. Get match information const match = await client.getMatch('TTT', 'a3c6e5f2-1a2b-4c5d-8e9f-0123456789ab'); console.log('Match retrieved:', match); // 2. Simulate game completion const results: MatchResult[] = [ { playerId: 'p1', place: 1, score: 1200 }, { playerId: 'p2', place: 2, score: 900 } ]; // 3. Submit results await client.finishMatch('TTT', match.matchId, results); console.log('Match completed successfully'); } catch (error) { console.error('Workflow error:', error); } }

React Hook Example

import { useState, useEffect } from 'react'; interface UseESFAApiConfig { client: ESFAApiClient; } interface UseMatchState { match: GameMatch | null; loading: boolean; error: string | null; } export function useMatch(gameCode: string, matchId: string, config: UseESFAApiConfig) { const [state, setState] = useState<UseMatchState>({ match: null, loading: true, error: null, }); useEffect(() => { let mounted = true; async function fetchMatch() { try { setState(prev => ({ ...prev, loading: true, error: null })); const match = await config.client.getMatch(gameCode, matchId); if (mounted) { setState({ match, loading: false, error: null }); } } catch (error) { if (mounted) { setState({ match: null, loading: false, error: error instanceof Error ? error.message : 'Unknown error', }); } } } if (gameCode && matchId) { fetchMatch(); } return () => { mounted = false; }; }, [gameCode, matchId, config.client]); const finishMatch = async (results: MatchResult[]) => { try { await config.client.finishMatch(gameCode, matchId, results); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } }; return { ...state, finishMatch, }; }

Python

Basic API Client

import requests import json import time from datetime import datetime, timezone from typing import Dict, List, Optional, Any from dataclasses import dataclass @dataclass class TokenData: token: str expires: datetime @dataclass class Player: id: str name: str status: str img_avatar_url: Optional[str] = None @dataclass class GameMatch: match_id: str game_code: str players: List[Player] @dataclass class MatchResult: player_id: str place: int score: float class ESFAApiClient: def __init__(self, base_url: str, auth_url: str, api_key: str, secret_key: str): self.base_url = base_url.rstrip('/') self.auth_url = auth_url.rstrip('/') self.api_key = api_key self.secret_key = secret_key self._current_token: Optional[TokenData] = None self._session = requests.Session() def _get_valid_token(self) -> str: if not self._current_token or self._is_token_expired(self._current_token): self._current_token = self._authenticate() return self._current_token.token def _is_token_expired(self, token_data: TokenData) -> bool: now = datetime.now(timezone.utc) # Add 5-minute buffer buffer_time = token_data.expires.replace(tzinfo=timezone.utc) - (now + timedelta(minutes=5)) return buffer_time.total_seconds() <= 0 def _authenticate(self) -> TokenData: url = f"{self.auth_url}/v1/oauth/token/machine" payload = { "apiKey": self.api_key, "secretKey": self.secret_key } response = self._session.post(url, json=payload) response.raise_for_status() data = response.json() return TokenData( token=data["data"]["token"], expires=datetime.fromisoformat(data["data"]["expires"].replace('Z', '+00:00')) ) def _make_request(self, method: str, endpoint: str, **kwargs) -> Any: token = self._get_valid_token() url = f"{self.base_url}{endpoint}" headers = kwargs.get('headers', {}) headers['Authorization'] = f'Bearer {token}' kwargs['headers'] = headers response = self._session.request(method, url, **kwargs) if response.status_code == 401: # Token might be expired, refresh and retry once self._current_token = self._authenticate() headers['Authorization'] = f'Bearer {self._current_token.token}' response = self._session.request(method, url, **kwargs) if not response.ok: try: error_data = response.json() error_msg = error_data.get('errors', [{}])[0].get('error', response.reason) except: error_msg = response.reason raise requests.HTTPError(f"API request failed: {response.status_code} - {error_msg}") if response.status_code == 204: return None return response.json() def get_match(self, game_code: str, match_id: str) -> GameMatch: data = self._make_request('GET', f'/api/v1/games/{game_code}/matches/{match_id}') players = [ Player( id=p['id'], name=p['name'], status=p['status'], img_avatar_url=p.get('imgAvatarUrl') ) for p in data['players'] ] return GameMatch( match_id=data['matchId'], game_code=data['gameCode'], players=players ) def finish_match(self, game_code: str, match_id: str, results: List[MatchResult]) -> None: payload = { "results": [ { "playerId": result.player_id, "place": result.place, "score": result.score } for result in results ] } self._make_request( 'POST', f'/api/v1/games/{game_code}/matches/{match_id}/finish', json=payload ) def close(self): self._session.close()

Usage Example

from datetime import timedelta import os # Initialize client client = ESFAApiClient( base_url=os.getenv('ESFA_API_BASE_URL', 'https://api.your-service.tld'), auth_url=os.getenv('ESFA_AUTH_URL', 'https://auth.your-service.tld'), api_key=os.getenv('ESFA_API_KEY'), secret_key=os.getenv('ESFA_SECRET_KEY') ) def complete_match_workflow(): try: # 1. Get match information match = client.get_match('TTT', 'a3c6e5f2-1a2b-4c5d-8e9f-0123456789ab') print(f"Match retrieved: {match.match_id}") # 2. Create results results = [ MatchResult(player_id='p1', place=1, score=1200.0), MatchResult(player_id='p2', place=2, score=900.0) ] # 3. Submit results client.finish_match('TTT', match.match_id, results) print("Match completed successfully") except Exception as error: print(f"Workflow error: {error}") finally: client.close() if __name__ == "__main__": complete_match_workflow()

Django Integration

# settings.py ESFA_API_CONFIG = { 'BASE_URL': 'https://api.your-service.tld', 'AUTH_URL': 'https://auth.your-service.tld', 'API_KEY': os.getenv('ESFA_API_KEY'), 'SECRET_KEY': os.getenv('ESFA_SECRET_KEY'), } # services.py from django.conf import settings from .esfa_client import ESFAApiClient class MatchService: def __init__(self): config = settings.ESFA_API_CONFIG self.client = ESFAApiClient(**config) def get_match_data(self, game_code: str, match_id: str) -> dict: try: match = self.client.get_match(game_code, match_id) return { 'success': True, 'data': match } except Exception as e: return { 'success': False, 'error': str(e) } def complete_match(self, game_code: str, match_id: str, results: list) -> dict: try: match_results = [ MatchResult(**result) for result in results ] self.client.finish_match(game_code, match_id, match_results) return {'success': True} except Exception as e: return { 'success': False, 'error': str(e) } # views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods import json @csrf_exempt @require_http_methods(["POST"]) def finish_match(request, game_code, match_id): try: data = json.loads(request.body) results = data.get('results', []) service = MatchService() result = service.complete_match(game_code, match_id, results) if result['success']: return JsonResponse({'status': 'success'}) else: return JsonResponse({'error': result['error']}, status=400) except Exception as e: return JsonResponse({'error': str(e)}, status=500)

Node.js/Express

Express Server Integration

const express = require('express'); const { ESFAApiClient } = require('./esfa-client'); const app = express(); app.use(express.json()); // Initialize ESFA client const esfaClient = new ESFAApiClient({ baseUrl: process.env.ESFA_API_BASE_URL, authUrl: process.env.ESFA_AUTH_URL, apiKey: process.env.ESFA_API_KEY, secretKey: process.env.ESFA_SECRET_KEY, }); // Middleware for error handling const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; // Get match endpoint app.get('/matches/:gameCode/:matchId', asyncHandler(async (req, res) => { const { gameCode, matchId } = req.params; try { const match = await esfaClient.getMatch(gameCode, matchId); res.json(match); } catch (error) { console.error('Get match error:', error); if (error.message.includes('404')) { return res.status(404).json({ error: 'Match not found' }); } if (error.message.includes('410')) { return res.status(410).json({ error: 'Match expired' }); } res.status(500).json({ error: 'Internal server error' }); } })); // Finish match endpoint app.post('/matches/:gameCode/:matchId/finish', asyncHandler(async (req, res) => { const { gameCode, matchId } = req.params; const { results } = req.body; // Validate request if (!Array.isArray(results) || results.length === 0) { return res.status(400).json({ error: 'Results array is required' }); } try { await esfaClient.finishMatch(gameCode, matchId, results); res.status(204).send(); } catch (error) { console.error('Finish match error:', error); if (error.message.includes('400')) { return res.status(400).json({ error: 'Invalid results data' }); } if (error.message.includes('404')) { return res.status(404).json({ error: 'Match not found' }); } if (error.message.includes('409')) { return res.status(409).json({ error: 'Match already finished' }); } res.status(500).json({ error: 'Internal server error' }); } })); // Error handling middleware app.use((error, req, res, next) => { console.error('Unhandled error:', error); res.status(500).json({ error: 'Internal server error' }); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });

Java

Spring Boot Integration

// ESFAApiClient.java @Component public class ESFAApiClient { private final RestTemplate restTemplate; private final String baseUrl; private final String authUrl; private final String apiKey; private final String secretKey; private TokenData currentToken; public ESFAApiClient( @Value("${esfa.api.base-url}") String baseUrl, @Value("${esfa.api.auth-url}") String authUrl, @Value("${esfa.api.key}") String apiKey, @Value("${esfa.api.secret}") String secretKey) { this.baseUrl = baseUrl; this.authUrl = authUrl; this.apiKey = apiKey; this.secretKey = secretKey; this.restTemplate = new RestTemplate(); } private String getValidToken() { if (currentToken == null || isTokenExpired(currentToken)) { currentToken = authenticate(); } return currentToken.getToken(); } private boolean isTokenExpired(TokenData tokenData) { return Instant.now().isAfter( tokenData.getExpires().minus(Duration.ofMinutes(5)) ); } private TokenData authenticate() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); Map<String, String> request = Map.of( "apiKey", apiKey, "secretKey", secretKey ); HttpEntity<Map<String, String>> entity = new HttpEntity<>(request, headers); ResponseEntity<AuthResponse> response = restTemplate.postForEntity( authUrl + "/v1/oauth/token/machine", entity, AuthResponse.class ); if (!response.getStatusCode().is2xxSuccessful()) { throw new RuntimeException("Authentication failed: " + response.getStatusCode()); } AuthResponse.Data data = response.getBody().getData(); return new TokenData(data.getToken(), Instant.parse(data.getExpires())); } private HttpHeaders createAuthHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(getValidToken()); return headers; } public GameMatch getMatch(String gameCode, String matchId) { HttpEntity<Void> entity = new HttpEntity<>(createAuthHeaders()); ResponseEntity<GameMatch> response = restTemplate.exchange( baseUrl + "/api/v1/games/" + gameCode + "/matches/" + matchId, HttpMethod.GET, entity, GameMatch.class ); return response.getBody(); } public void finishMatch(String gameCode, String matchId, List<MatchResult> results) { Map<String, Object> request = Map.of("results", results); HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request, createAuthHeaders()); restTemplate.exchange( baseUrl + "/api/v1/games/" + gameCode + "/matches/" + matchId + "/finish", HttpMethod.POST, entity, Void.class ); } } // MatchController.java @RestController @RequestMapping("/api/matches") @Slf4j public class MatchController { @Autowired private ESFAApiClient esfaClient; @GetMapping("/{gameCode}/{matchId}") public ResponseEntity<GameMatch> getMatch( @PathVariable String gameCode, @PathVariable String matchId) { try { GameMatch match = esfaClient.getMatch(gameCode, matchId); return ResponseEntity.ok(match); } catch (HttpClientErrorException.NotFound e) { return ResponseEntity.notFound().build(); } catch (Exception e) { log.error("Error getting match", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/{gameCode}/{matchId}/finish") public ResponseEntity<Void> finishMatch( @PathVariable String gameCode, @PathVariable String matchId, @RequestBody FinishMatchRequest request) { try { esfaClient.finishMatch(gameCode, matchId, request.getResults()); return ResponseEntity.noContent().build(); } catch (HttpClientErrorException.BadRequest e) { return ResponseEntity.badRequest().build(); } catch (HttpClientErrorException.NotFound e) { return ResponseEntity.notFound().build(); } catch (HttpClientErrorException.Conflict e) { return ResponseEntity.status(HttpStatus.CONFLICT).build(); } catch (Exception e) { log.error("Error finishing match", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } }

PHP

Laravel Integration

<?php // ESFAApiClient.php class ESFAApiClient { private $baseUrl; private $authUrl; private $apiKey; private $secretKey; private $currentToken; private $client; public function __construct($baseUrl, $authUrl, $apiKey, $secretKey) { $this->baseUrl = rtrim($baseUrl, '/'); $this->authUrl = rtrim($authUrl, '/'); $this->apiKey = $apiKey; $this->secretKey = $secretKey; $this->client = new \GuzzleHttp\Client(); } private function getValidToken() { if (!$this->currentToken || $this->isTokenExpired($this->currentToken)) { $this->currentToken = $this->authenticate(); } return $this->currentToken['token']; } private function isTokenExpired($tokenData) { $expiresAt = new DateTime($tokenData['expires']); $now = new DateTime(); $buffer = new DateInterval('PT5M'); // 5 minutes return $now >= $expiresAt->sub($buffer); } private function authenticate() { $response = $this->client->post($this->authUrl . '/v1/oauth/token/machine', [ 'json' => [ 'apiKey' => $this->apiKey, 'secretKey' => $this->secretKey, ], 'headers' => [ 'Content-Type' => 'application/json', ], ]); $data = json_decode($response->getBody(), true); return $data['data']; } private function makeRequest($method, $endpoint, $options = []) { $token = $this->getValidToken(); $url = $this->baseUrl . $endpoint; $defaultOptions = [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json', ], ]; $options = array_merge_recursive($defaultOptions, $options); try { $response = $this->client->request($method, $url, $options); } catch (\GuzzleHttp\Exception\ClientException $e) { if ($e->getResponse()->getStatusCode() === 401) { // Retry with fresh token $this->currentToken = $this->authenticate(); $options['headers']['Authorization'] = 'Bearer ' . $this->currentToken['token']; $response = $this->client->request($method, $url, $options); } else { throw $e; } } if ($response->getStatusCode() === 204) { return null; } return json_decode($response->getBody(), true); } public function getMatch($gameCode, $matchId) { return $this->makeRequest('GET', "/api/v1/games/{$gameCode}/matches/{$matchId}"); } public function finishMatch($gameCode, $matchId, $results) { return $this->makeRequest('POST', "/api/v1/games/{$gameCode}/matches/{$matchId}/finish", [ 'json' => ['results' => $results], ]); } } // MatchController.php (Laravel) class MatchController extends Controller { private $esfaClient; public function __construct() { $this->esfaClient = new ESFAApiClient( config('services.esfa.base_url'), config('services.esfa.auth_url'), config('services.esfa.api_key'), config('services.esfa.secret_key') ); } public function getMatch($gameCode, $matchId) { try { $match = $this->esfaClient->getMatch($gameCode, $matchId); return response()->json($match); } catch (\GuzzleHttp\Exception\ClientException $e) { $statusCode = $e->getResponse()->getStatusCode(); switch ($statusCode) { case 404: return response()->json(['error' => 'Match not found'], 404); case 410: return response()->json(['error' => 'Match expired'], 410); default: return response()->json(['error' => 'Client error'], $statusCode); } } catch (Exception $e) { \Log::error('Error getting match: ' . $e->getMessage()); return response()->json(['error' => 'Internal server error'], 500); } } public function finishMatch(Request $request, $gameCode, $matchId) { $request->validate([ 'results' => 'required|array|min:1', 'results.*.playerId' => 'required|string', 'results.*.place' => 'required|integer|min:1', 'results.*.score' => 'required|numeric', ]); try { $this->esfaClient->finishMatch($gameCode, $matchId, $request->results); return response()->noContent(); } catch (\GuzzleHttp\Exception\ClientException $e) { $statusCode = $e->getResponse()->getStatusCode(); switch ($statusCode) { case 400: return response()->json(['error' => 'Invalid results data'], 400); case 404: return response()->json(['error' => 'Match not found'], 404); case 409: return response()->json(['error' => 'Match already finished'], 409); default: return response()->json(['error' => 'Client error'], $statusCode); } } catch (Exception $e) { \Log::error('Error finishing match: ' . $e->getMessage()); return response()->json(['error' => 'Internal server error'], 500); } } }

Testing Examples

JavaScript/Jest Testing

// __tests__/esfa-client.test.js import { ESFAApiClient } from '../src/esfa-client'; // Mock fetch global.fetch = jest.fn(); describe('ESFAApiClient', () => { let client; beforeEach(() => { client = new ESFAApiClient({ baseUrl: 'https://api.test.tld', authUrl: 'https://auth.test.tld', apiKey: 'test-key', secretKey: 'test-secret', }); // Reset fetch mock fetch.mockClear(); }); describe('authentication', () => { it('should authenticate successfully', async () => { // Mock successful auth response fetch.mockResolvedValueOnce({ ok: true, json: async () => ({ data: { token: 'test-token', expires: '2025-09-05T13:31:23.174Z' } }) }); // Mock API call fetch.mockResolvedValueOnce({ ok: true, json: async () => ({ matchId: 'test-match', gameCode: 'TTT', players: [] }) }); const match = await client.getMatch('TTT', 'test-match'); expect(fetch).toHaveBeenCalledTimes(2); expect(match.matchId).toBe('test-match'); }); it('should handle auth failure', async () => { fetch.mockRejectedValueOnce(new Error('Auth failed')); await expect(client.getMatch('TTT', 'test-match')).rejects.toThrow('Auth failed'); }); }); describe('getMatch', () => { beforeEach(() => { // Mock auth fetch.mockResolvedValueOnce({ ok: true, json: async () => ({ data: { token: 'test-token', expires: '2025-09-05T13:31:23.174Z' } }) }); }); it('should get match successfully', async () => { const mockMatch = { matchId: 'test-match', gameCode: 'TTT', players: [ { id: 'p1', name: 'Alice', status: 'Active' } ] }; fetch.mockResolvedValueOnce({ ok: true, json: async () => mockMatch }); const match = await client.getMatch('TTT', 'test-match'); expect(match).toEqual(mockMatch); }); it('should handle 404 error', async () => { fetch.mockResolvedValueOnce({ ok: false, status: 404, json: async () => ({ errors: [{ error: 'Match not found' }] }) }); await expect(client.getMatch('TTT', 'invalid-match')) .rejects.toThrow('Match not found'); }); }); });

Python Testing

# test_esfa_client.py import pytest import requests_mock from datetime import datetime, timezone from esfa_client import ESFAApiClient, MatchResult @pytest.fixture def client(): return ESFAApiClient( base_url='https://api.test.tld', auth_url='https://auth.test.tld', api_key='test-key', secret_key='test-secret' ) @pytest.fixture def auth_response(): return { "data": { "token": "test-token", "expires": "2025-09-05T13:31:23.174Z" } } class TestESFAApiClient: def test_authentication(self, client, auth_response): with requests_mock.Mocker() as m: # Mock auth endpoint m.post('https://auth.test.tld/v1/oauth/token/machine', json=auth_response) # Mock API endpoint match_data = { "matchId": "test-match", "gameCode": "TTT", "players": [] } m.get('https://api.test.tld/api/v1/games/TTT/matches/test-match', json=match_data) match = client.get_match('TTT', 'test-match') assert match.match_id == 'test-match' assert match.game_code == 'TTT' def test_finish_match(self, client, auth_response): with requests_mock.Mocker() as m: # Mock auth m.post('https://auth.test.tld/v1/oauth/token/machine', json=auth_response) # Mock finish endpoint m.post( 'https://api.test.tld/api/v1/games/TTT/matches/test-match/finish', status_code=204 ) results = [ MatchResult(player_id='p1', place=1, score=1200.0), MatchResult(player_id='p2', place=2, score=900.0) ] # Should not raise exception client.finish_match('TTT', 'test-match', results) def test_error_handling(self, client, auth_response): with requests_mock.Mocker() as m: # Mock auth m.post('https://auth.test.tld/v1/oauth/token/machine', json=auth_response) # Mock 404 response m.get( 'https://api.test.tld/api/v1/games/TTT/matches/invalid-match', status_code=404, json={"errors": [{"error": "Match not found"}]} ) with pytest.raises(requests.HTTPError): client.get_match('TTT', 'invalid-match')

Best Practices Summary

Error Handling

  • Always implement retry logic for 401 errors (token expiration)

  • Don't retry client errors (4xx) except 401

  • Use exponential backoff for server errors (5xx)

  • Log errors with sufficient context but avoid sensitive data

Security

  • Store credentials in environment variables or secure configuration

  • Never log Bearer tokens

  • Implement token refresh before expiration

  • Use HTTPS in production

Performance

  • Reuse HTTP connections when possible

  • Implement connection pooling

  • Cache tokens until near expiration

  • Set appropriate timeouts

Testing

  • Mock external API calls in unit tests

  • Test error scenarios and edge cases

  • Validate request/response formats

  • Test token refresh scenarios

05 September 2025