WakaStart

Erreurs courantes

Format d'erreur unifié, codes HTTP, codes métier et stratégies de gestion pour une Wakapp robuste.

Version v1.06 min de lecture

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 :

ChampTypeDescription
statusCodenumberCode HTTP
messagestring | string[]Description lisible du problème
errorstringLibellé 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 (teamLevel doit être NONE | 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 :

  1. Header x-enriched-token absent — vérifier le proxy serveur
  2. Cookie wakastart_token non posé (l'appel à /api/auth/enrich a échoué)
  3. Token enrichi expiré — procéder au refresh
  4. Claim organization absent du JWT Keycloak (Organizations Keycloak non configurées)
  5. keycloak_id en DB ne correspond pas au sub Keycloak

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 CustomerAdmin qui essaie d'accéder à un Customer qui ne lui appartient pas verra un 404 (pas un 403). 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 PENDING dé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 :

NiveauFenêtreLimite
Court1 s10 req
Moyen60 s100 req
Long3 600 s1 000 req

Limites spécifiques :

EndpointLimite
/discover/start-login5/s · 20/10s · 100/60s
/api/auth/enrich5/min
/api/auth/logout10/min
/api/auth/token/refresh30/min
/api/invitations/verify/:token10/min/IP
/api/invitations/accept/:token5/min/IP

Action : Respecter le Retry-After et implémenter un backoff exponentiel.

typescript
const 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'/accept invitation)

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 :

MessageStatusSignification
"Token enrichi manquant ou invalide"401wakaToken absent ou invalide
"Insufficient credits"402Quota crédits dépassé
"La fonctionnalité invitation n'est pas activée pour ce customer"403Feature invitation désactivée
"Invalid API key"401Clé 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 verify retournent toujours 200 avec { valid: false, errorMessage: "..." } — pas un code d'erreur HTTP.


Gestion d'erreurs recommandée côté frontend

typescript
class 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

Cette page vous a-t-elle été utile ?