Gestion d'erreurs
Format d'erreur uniforme
Toutes les erreurs HTTP retournées par apps/api suivent le format NestJS standard :
{
"statusCode": 422,
"message": "Validation failed",
"error": "Unprocessable Entity",
"details": [
{
"path": "consents.healthData",
"message": "Required"
}
]
}Le champ details est présent pour les erreurs Zod (422 Unprocessable Entity) et liste les chemins ayant échoué.
Idempotency keys
Pour tout endpoint mutant sensible (création d'alerte, déclenchement d'appel aidant, escalade), le client doit fournir un header :
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000La clé est un UUID v4 généré côté client. Le back-end stocke un cache (TTL 24 h) { idempotencyKey, hashedRequest, response } :
- Même clé et même payload, réponse cachée renvoyée (HTTP 200/201 dupliqué)
- Même clé et payload différent, HTTP
409 Conflict - Clé absente sur endpoint exigeant, HTTP
400 Bad Request
Les endpoints qui exigent une Idempotency-Key :
POST /v1/alerts/button-pressedPOST /v1/assistance/contact-aidantPOST /v1/livekit/token(optionnel mais recommandé)
Rate limits
Le module RateLimitGuard applique un seuil par senior_id et endpoint_signature :
| Endpoint | Seuil | Fenêtre |
|---|---|---|
POST /v1/auth/login | 5 / IP | 60 s |
POST /v1/auth/refresh | 30 / refresh-token | 60 s |
POST /v1/assistance/contact-aidant | 3 / senior | 5 min (anti-spam aidants) |
POST /v1/widgets/*/trigger (dev) | 60 / senior | 60 s |
| Default REST | 120 / IP | 60 s |
Au-delà du seuil, HTTP 429 Too Many Requests avec header Retry-After (secondes).
Retry policy côté front
Pour les 429 et les erreurs 5xx (sauf 501 Not Implemented), le front doit appliquer un exponential backoff avec jitter :
async function callWithRetry<T>(
fn: () => Promise<Response>,
maxAttempts = 4
): Promise<T> {
let lastError: unknown;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fn();
if (response.ok) {
return (await response.json()) as T;
}
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(`HTTP ${response.status}`);
}
lastError = await response.text();
const retryAfter = Number(response.headers.get("Retry-After") ?? 0);
const backoff = retryAfter > 0
? retryAfter * 1000
: Math.min(30_000, 500 * 2 ** attempt) + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, backoff));
}
throw new Error(`Retries exhausted: ${String(lastError)}`);
}Codes d'erreur communs
| HTTP | Sens | Action côté front |
|---|---|---|
400 | Payload mal formé | Vérifier le contrat Zod, ne pas retenter |
401 | JWT manquant ou expiré | Refresh puis retry une fois |
403 | Rôle insuffisant | Pas de retry, bug d'intégration |
404 | Ressource introuvable | Pas de retry |
409 | Conflit (idempotency, état inattendu) | Inspecter le body |
422 | Validation Zod KO | Inspecter details[] |
429 | Rate limit | Backoff respect Retry-After |
500-599 | Erreur back-end | Exponential backoff |
503 | Service en maintenance ou cold-start | Retry après 5-10 s |
Voice pipeline, codes spécifiques
Sur le canal kohami.agent.events, le back-end peut publier des events de fallback :
stt_fallback_active, bascule STT moderealtimeversbatchou inversementllm_fallback_active, bascule modèle agent (Medium 3 vers Large 3) sur dégradationtts_fallback_active, bascule voix TTS
Le front les log mais ne déclenche aucune action UI. C'est purement informationnel pour le monitoring.
Validation par défaut
Tous les inputs REST sont validés via Zod (côté NestJS via ZodValidationPipe). Tous les events DataChannel publiés par le back-end sont validés via Zod avant room.localParticipant.publishData(). Cf. ADR div-014-zod-vs-class-validator.md.