KOHAMI Docs
Guides

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-446655440000

La 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-pressed
  • POST /v1/assistance/contact-aidant
  • POST /v1/livekit/token (optionnel mais recommandé)

Rate limits

Le module RateLimitGuard applique un seuil par senior_id et endpoint_signature :

EndpointSeuilFenêtre
POST /v1/auth/login5 / IP60 s
POST /v1/auth/refresh30 / refresh-token60 s
POST /v1/assistance/contact-aidant3 / senior5 min (anti-spam aidants)
POST /v1/widgets/*/trigger (dev)60 / senior60 s
Default REST120 / IP60 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 :

retry-with-backoff.ts
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

HTTPSensAction côté front
400Payload mal forméVérifier le contrat Zod, ne pas retenter
401JWT manquant ou expiréRefresh puis retry une fois
403Rôle insuffisantPas de retry, bug d'intégration
404Ressource introuvablePas de retry
409Conflit (idempotency, état inattendu)Inspecter le body
422Validation Zod KOInspecter details[]
429Rate limitBackoff respect Retry-After
500-599Erreur back-endExponential backoff
503Service en maintenance ou cold-startRetry 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 mode realtime vers batch ou inversement
  • llm_fallback_active, bascule modèle agent (Medium 3 vers Large 3) sur dégradation
  • tts_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.