Erreurs courantes
Format d'erreur unifié, codes HTTP, codes métier et stratégies de gestion pour une Wakapp robuste.
Erreurs courantes
L'API WakaStart utilise un format d'erreur unifié (NestJS standard). Chaque code HTTP a une sémantique précise. Ce chapitre liste les cas concrets et les réponses attendues.
Format d'erreur unifié
json{ "statusCode": 403, "message": "Niveau d'administration insuffisant. Requis: CustomerAdmin, Actuel: User", "error": "Forbidden" }
Pour les erreurs de validation (400) avec plusieurs champs invalides :
json{ "statusCode": 400, "message": [ "email must be an email", "firstName should not be empty", "profileId must be a UUID" ], "error": "Bad Request" }
Champs :
| Champ | Type | Description |
|---|---|---|
statusCode | number | Code HTTP |
message | string | string[] | Description lisible du problème |
error | string | Libellé HTTP standard |
400 — Bad Request
Données invalides. Le body n'a pas passé la validation DTO.
Causes fréquentes :
- Email non valide
- Email de domaine jetable (yopmail, mailinator…)
- UUID manquant ou malformé
- Champ obligatoire absent
- Valeur hors enum (
teamLeveldoit êtreNONE | VIEWER | CONTRIBUTOR | MEMBER | MANAGER | ADMIN) - Token d'invitation invalide/expiré/déjà utilisé (lors de
/accept)
Payload exemple :
json{ "statusCode": 400, "message": ["invitedEmail must be an email", "profileId must be a UUID"], "error": "Bad Request" }
Action : Corriger le body de la requête et retenter.
401 — Unauthorized
Authentification manquante ou invalide.
Cas 1 — Token enrichi manquant
json{ "statusCode": 401, "message": "Token enrichi manquant ou invalide", "error": "Unauthorized" }
Causes :
- Header
x-enriched-tokenabsent — vérifier le proxy serveur - Cookie
wakastart_tokennon posé (l'appel à/api/auth/enricha échoué) - Token enrichi expiré — procéder au refresh
- Claim
organizationabsent du JWT Keycloak (Organizations Keycloak non configurées) keycloak_iden DB ne correspond pas ausubKeycloak
Action : Tenter un refresh token (une seule fois). Si le refresh échoue → redirect login.
Cas 2 — Token Bearer expiré ou invalide
json{ "statusCode": 401, "message": "Unauthorized", "error": "Unauthorized" }
Action : Refresh proactif ou redirect login.
Cas 3 — API Key invalide
json{ "statusCode": 401, "message": "Invalid API key", "error": "Unauthorized" }
Action : Vérifier la clé API (format sk_live_... ou sk_test_...).
402 — Payment Required
Crédits insuffisants pour une opération consommant des crédits (import CSV, traduction IA, scan antivirus sur quota).
json{ "statusCode": 402, "message": "Insufficient credits", "error": "Payment Required" }
Action : Rediriger vers la page d'achat de crédits (/billing/credits).
403 — Forbidden
L'utilisateur est authentifié mais n'a pas les droits nécessaires.
Cas 1 — Niveau admin insuffisant
json{ "statusCode": 403, "message": "Niveau d'administration insuffisant. Requis: CustomerAdmin, Actuel: User", "error": "Forbidden" }
Cas 2 — Droit applicatif manquant
json{ "statusCode": 403, "message": "Droit requis : users.create", "error": "Forbidden" }
Cas 3 — Feature désactivée
json{ "statusCode": 403, "message": "La fonctionnalité invitation n'est pas activée pour ce customer", "error": "Forbidden" }
Action : Afficher un message clair à l'utilisateur. Ne pas retenter automatiquement — c'est un problème de configuration, pas réseau.
404 — Not Found
L'entité demandée n'existe pas ou n'est pas visible depuis le scope du token.
json{ "statusCode": 404, "message": "Customer introuvable", "error": "Not Found" }
Anti-BOLA : un
CustomerAdminqui essaie d'accéder à un Customer qui ne lui appartient pas verra un404(pas un403). C'est intentionnel pour ne pas révéler l'existence de l'entité (OWASP BOLA pattern).
Action : Vérifier que l'identifiant (WID ou UUID) est correct et appartient au tenant accessible.
409 — Conflict
Une contrainte d'unicité est violée.
json{ "statusCode": 409, "message": "Un utilisateur avec cet email existe déjà dans ce Customer", "error": "Conflict" }
Cas courants :
- Email déjà utilisé dans le Customer (invitation ou utilisateur existant)
- Invitation
PENDINGdéjà existante pour cet email + Customer - Subdomain déjà utilisé par un autre Network
- WID déjà utilisé
Action : Adapter l'UI pour informer l'utilisateur du conflit. Proposer une alternative (ex: renvoyer l'invitation existante plutôt que d'en créer une nouvelle).
429 — Too Many Requests
Rate limit dépassé.
json{ "statusCode": 429, "message": "Too Many Requests" }
Headers : Retry-After: 2 (durée en secondes avant de pouvoir retenter)
Limites par défaut :
| Niveau | Fenêtre | Limite |
|---|---|---|
| Court | 1 s | 10 req |
| Moyen | 60 s | 100 req |
| Long | 3 600 s | 1 000 req |
Limites spécifiques :
| Endpoint | Limite |
|---|---|
/discover/start-login | 5/s · 20/10s · 100/60s |
/api/auth/enrich | 5/min |
/api/auth/logout | 10/min |
/api/auth/token/refresh | 30/min |
/api/invitations/verify/:token | 10/min/IP |
/api/invitations/accept/:token | 5/min/IP |
Action : Respecter le Retry-After et implémenter un backoff exponentiel.
typescriptconst retryAfter = parseInt(res.headers.get("Retry-After") ?? "2", 10); await new Promise(r => setTimeout(r, retryAfter * 1000)); // 1 seul retry, puis échouer proprement
5xx — Erreurs serveur
json{ "statusCode": 500, "message": "Internal server error", "error": "Internal Server Error" }
Causes possibles :
- Service downstream injoignable (Keycloak, ws-serv-config, ws-serv-token)
- Erreur Prisma (contrainte DB inattendue)
- Timeout interne
- Problème de provisioning Keycloak (lors d'
/acceptinvitation)
Action : Retenter avec backoff exponentiel (max 3 tentatives). Si persistant, contacter le support avec le request-id (présent dans les headers de réponse).
typescript// Toujours logger le request-id pour le support const requestId = res.headers.get("x-request-id"); if (requestId) logger.error("API 500", { requestId, url, method });
Codes d'erreur métier (messages stricts)
Certains message ont une valeur exacte et stable que vous pouvez tester programmatiquement :
| Message | Status | Signification |
|---|---|---|
"Token enrichi manquant ou invalide" | 401 | wakaToken absent ou invalide |
"Insufficient credits" | 402 | Quota crédits dépassé |
"La fonctionnalité invitation n'est pas activée pour ce customer" | 403 | Feature invitation désactivée |
"Invalid API key" | 401 | Clé API inconnue ou révoquée |
"Invitation non trouvée" | — | Token d'invitation inconnu (via verify) |
"Cette invitation a déjà été acceptée" | — | Token déjà utilisé |
"Cette invitation a expiré" | — | expiresAt < now() |
"Cette invitation a été annulée" | — | Statut CANCELLED |
Note : les messages
verifyretournent toujours200avec{ valid: false, errorMessage: "..." }— pas un code d'erreur HTTP.
Gestion d'erreurs recommandée côté frontend
typescriptclass ApiError extends Error { constructor( public readonly status: number, public readonly body: { statusCode: number; message: string | string[]; error: string } ) { super(Array.isArray(body.message) ? body.message.join(", ") : body.message); } get isForbidden() { return this.status === 403; } get isUnauthorized(){ return this.status === 401; } get isConflict() { return this.status === 409; } get isRateLimit() { return this.status === 429; } get isServerError() { return this.status >= 500; } } // Usage dans un composant React try { await createUser(dto); toast.success("Utilisateur créé"); } catch (err) { if (err instanceof ApiError) { if (err.isConflict) toast.error("Cet email est déjà utilisé dans ce Customer"); else if (err.isForbidden) toast.error("Vous n'avez pas les droits pour cette action"); else if (err.isRateLimit) toast.error("Trop de requêtes — veuillez patienter"); else toast.error("Erreur inattendue. Contactez le support."); } }
Aller plus loin
- Pièges classiques : diagnostic détaillé des erreurs fréquentes
- Bonnes pratiques : patterns de gestion d'erreurs avancés
- Référence des endpoints : limites de rate par endpoint