Using the API
Errors — Business Codes
Wakastart business error codes, exact messages that can be tested programmatically, and status mappings.
Version v1.03 min read
Errors — Business Codes
This chapter lists error messages with an exact, stable value that you can test programmatically. Read HTTP errors for the standard HTTP codes.
Strict messages (testable programmatically)
Some message values have an exact, stable value:
| Message | Status | Meaning |
|---|---|---|
"Token enrichi manquant ou invalide" | 401 | wakaToken missing or invalid |
"Insufficient credits" | 402 | Credit quota exceeded |
"La fonctionnalité invitation n'est pas activée pour ce customer" | 403 | invitation feature disabled |
"Cle API manquante (header x-api-key)" | 401 | x-api-key header absent |
"Cle API utilisateur invalide ou expiree" | 401 | User API key invalid, revoked, or expired |
"Cle API invalide" | 401 | Unknown API key |
"Invitation non trouvée" | 200 (valid: false) | Unknown invitation token (via verify) |
"Cette invitation a déjà été acceptée" | 200 (valid: false) | Token already used |
"Cette invitation a expiré" | 200 (valid: false) | expiresAt < now() |
"Cette invitation a été annulée" | 200 (valid: false) | Status CANCELLED |
Note: the
verifymessages always return200with{ valid: false, errorMessage: "..." }— not an HTTP error code.
Invitation errors — exhaustive handling
typescript// Handle the 4 error states of an invitation async function loadInvitation(token: string) { const res = await fetch(`/api/proxy/invitations/verify/${token}`); const { valid, invitation, errorMessage } = await res.json(); if (!valid) { switch (errorMessage) { case "Cette invitation a expiré": return { error: "expired", ui: "Ce lien d'invitation a expiré. Demandez un nouveau lien." }; case "Cette invitation a déjà été acceptée": return { error: "accepted", ui: "Ce compte existe déjà. Connectez-vous directement." }; case "Cette invitation a été annulée": return { error: "cancelled", ui: "Cette invitation a été annulée par l'administrateur." }; case "Invitation non trouvée": default: return { error: "not_found", ui: "Ce lien d'invitation est invalide." }; } } return { valid: true, invitation }; }
Rights errors — structured messages
403 errors related to rights contain structured information in message:
typescript// Extract the required vs actual level from a 403 function parseAdminLevelError(message: string) { const match = message.match(/Requis: (\w+), Actuel: (\w+)/); if (match) { return { required: match[1], actual: match[2] }; } return null; } // Usage const error = parseAdminLevelError( "Niveau d'administration insuffisant. Requis: CustomerAdmin, Actuel: User" ); // → { required: "CustomerAdmin", actual: "User" }
Rate limit codes by endpoint
Some endpoints have specific rate limit messages that let you identify the endpoint concerned:
typescript// Rate limit detection pattern function isRateLimitError(status: number, headers: Headers): boolean { return status === 429 && headers.has("Retry-After"); } function getRateLimitWait(headers: Headers): number { return parseInt(headers.get("Retry-After") ?? "2", 10) * 1000; }
Validation errors — parsing
For 400s with an array of messages, build a map of fields in error:
typescriptinterface ValidationErrors { [field: string]: string[]; } function parseValidationErrors( messages: string | string[] ): ValidationErrors { const msgs = Array.isArray(messages) ? messages : [messages]; const result: ValidationErrors = {}; for (const msg of msgs) { // NestJS format: "fieldName must be ..." const firstSpace = msg.indexOf(" "); if (firstSpace > -1) { const field = msg.substring(0, firstSpace); const error = msg.substring(firstSpace + 1); result[field] = [...(result[field] ?? []), error]; } } return result; } // Usage with react-hook-form try { await createUser(dto); } catch (err) { if (err instanceof ApiError && err.status === 400) { const fieldErrors = parseValidationErrors(err.body.message); Object.entries(fieldErrors).forEach(([field, errors]) => { form.setError(field as any, { message: errors[0] }); }); } }
Conflict errors (409) — strategies
typescripttry { await createInvitation(dto); toast.success("Invitation envoyée"); } catch (err) { if (err instanceof ApiError && err.isConflict) { const msg = err.body.message as string; if (msg.includes("invitation") && msg.includes("PENDING")) { // Invitation already exists — offer to resend setConflictAction("resend"); } else if (msg.includes("utilisateur")) { // User already in the Customer toast.error("Cet email est déjà utilisé dans ce Customer."); } } }
Go further
- HTTP errors: full 400-5xx codes
- Invitations — Verification: handling
valid: false - Common pitfalls: diagnosing recurring errors