Utilisation de l'API
Authentifier les requêtes, gérer la pagination, le multi-tenant, la runtime config et les conventions REST.
Utilisation de l'API
Toutes les requêtes post-auth passent par la Public API (
ws-back-api :3005). Ce chapitre couvre les conventions REST, l'authentification des requêtes, la pagination, le multi-tenant et les patterns d'architecture recommandés.
Pourquoi cette étape
L'API WakaStart a des conventions spécifiques (double token, pagination par page, filtrage automatique multi-tenant) que votre code doit respecter. Un seul header manquant entraîne un 401. Un limit trop grand risque un timeout.
Concepts clés
- Bearer + enriched : double authentification obligatoire pour les appels frontend.
- API Key : alternative pour les backends serveur-à-serveur.
- WID : identifiant court préféré dans les URLs et les paramètres.
- Filtrage automatique : le backend filtre selon le scope du token — vous ne filtrez jamais manuellement.
- Pagination : page-based (page + limit), pas cursor-based.
Authentification des requêtes
Méthode 1 — Bearer JWT + enriched token (frontend)
Une requête vers les services downstream doit porter deux tokens :
httpAuthorization: Bearer <keycloak_access_token> (RS256, émis par Keycloak) x-enriched-token: <wakastart_token> (HS256, émis par ws-serv-token)
Sans x-enriched-token, la réponse est 401 Token enrichi manquant ou invalide.
Implémentation recommandée — proxy serveur Next.js :
typescript// app/api/proxy/[...path]/route.ts import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; export async function GET( req: NextRequest, { params }: { params: { path: string[] } } ) { const cookieStore = await cookies(); const accessToken = cookieStore.get("keycloak_token")?.value; const enrichedToken = cookieStore.get("wakastart_token")?.value; if (!accessToken || !enrichedToken) { return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); } const apiUrl = `${process.env.WAKASTART_API_URL}/api/${params.path.join("/")}`; const url = new URL(apiUrl); // Transférer les query params req.nextUrl.searchParams.forEach((value, key) => { url.searchParams.set(key, value); }); const response = await fetch(url.toString(), { method: req.method, headers: { "Authorization": `Bearer ${accessToken}`, "x-enriched-token": enrichedToken, "Content-Type": "application/json", }, }); const data = await response.json(); return NextResponse.json(data, { status: response.status }); } // Identique pour POST/PATCH/DELETE en forwardant le body export async function POST(req: NextRequest, ctx: { params: { path: string[] } }) { const body = await req.json(); // ... même logique avec body: JSON.stringify(body) }
Depuis votre frontend React :
typescript// lib/api.ts — utilise le proxy qui gère l'auth async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> { const url = new URL(`/api/proxy/${path}`, window.location.origin); if (params) { Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); } const res = await fetch(url.toString()); if (!res.ok) throw new ApiError(res.status, await res.json()); return res.json(); } // Usage : const users = await apiGet<UserList>("config/users", { page: "1", limit: "20" });
Méthode 2 — API Key (backend serveur-à-serveur)
Les API Keys remplacent entièrement la paire Bearer+enriched. Elles sont scopées à un Customer.
typescriptconst response = await fetch(`${WAKASTART_API_URL}/api/config/users`, { headers: { "x-api-key": "sk_live_...", "Content-Type": "application/json", }, });
Les API Keys sont des secrets — ne les exposez jamais côté frontend, ne les committez jamais.
Conventions REST
| Convention | Détail |
|---|---|
| Verbes HTTP | GET (lecture), POST (création), PATCH (mise à jour partielle), PUT (remplacement), DELETE (suppression) |
| Format date | ISO 8601 avec timezone : 2026-05-19T10:30:00.000Z |
| Identifiants dans les URLs | Préférer les WIDs (/config/users/WKST05) aux UUIDs |
| Content-Type | application/json sur tous les POST/PATCH/PUT |
| Encodage | UTF-8 obligatoire |
| Nullabilité | Les champs absents ne sont pas retournés (pas de null explicite sauf si documenté) |
Format d'erreur unifié
json{ "statusCode": 403, "message": "Niveau d'administration insuffisant. Requis: CustomerAdmin, Actuel: User", "error": "Forbidden" }
Pour les erreurs de validation (400) :
json{ "statusCode": 400, "message": [ "email must be an email", "firstName should not be empty" ], "error": "Bad Request" }
Pagination
Tous les endpoints de liste supportent la pagination par page :
httpGET /api/config/users?page=1&limit=20
json{ "data": [ { "id": "uuid", "wid": "WKST05", "email": "john@acme.com", ... } ], "meta": { "total": 142, "page": 1, "limit": 20, "totalPages": 8 } }
Paramètres :
| Param | Défaut | Max | Description |
|---|---|---|---|
page | 1 | — | Numéro de page (1-indexed) |
limit | 20 | 100 | Nombre d'éléments par page |
Pattern avec TanStack Query :
typescriptimport { useQuery } from "@tanstack/react-query"; function useUsers(page: number) { return useQuery({ queryKey: ["users", page], queryFn: () => apiGet<UserList>("config/users", { page: String(page), limit: "20" }), placeholderData: (prev) => prev, // Éviter le flash blanc entre pages }); }
Filtres communs
La plupart des endpoints de liste acceptent des paramètres de filtrage :
httpGET /api/config/users?search=john&isActive=true&page=1&limit=20 GET /api/config/customers?networkId=NET001&page=1 GET /api/invitations/sent?status=PENDING&page=1&limit=10
Les paramètres disponibles sont documentés dans le Swagger (/api/docs en local).
Multi-tenant automatique
Les données sont automatiquement filtrées selon le scope du token :
adminLevel | Portée des données retournées |
|---|---|
WakaAdmin | Toute la plateforme |
OwnerAdmin | Son Partner entier |
NetworkAdmin | Son Network + tous ses Customers |
CustomerAdmin | Son Customer uniquement |
User | Son propre profil |
Vous n'avez pas besoin de filtrer manuellement. Si un CustomerAdmin appelle GET /api/config/users, il ne voit que les utilisateurs de son Customer — même s'il omet tout paramètre de filtre.
Runtime config
Pour charger le thème, les langues et le branding de votre app en un seul appel :
httpGET /api/apps/{appWid}/runtime-config Authorization: Bearer <token> x-enriched-token: <wakaToken>
json{ "theme": { "id": "uuid", "name": "Default", "cssUrl": "https://storage.wakastart.app/themes/default.css" }, "languages": [ { "code": "fr", "name": "Français", "isDefault": true, "i18nUrl": "https://storage.wakastart.app/i18n/fr.json" }, { "code": "en", "name": "English", "isDefault": false, "i18nUrl": "https://storage.wakastart.app/i18n/en.json" } ], "branding": { "logoLightUrl": "https://storage.wakastart.app/logos/acme-light.png", "logoDarkUrl": "https://storage.wakastart.app/logos/acme-dark.png", "faviconUrl": "https://storage.wakastart.app/favicons/acme.ico" } }
Pattern d'initialisation (Next.js App Router) :
typescript// app/layout.tsx — au chargement de l'app import { getRuntimeConfig } from "@/lib/runtime-config"; export default async function RootLayout({ children }: { children: React.ReactNode }) { const config = await getRuntimeConfig(process.env.APP_WID!); return ( <html> <head> <link rel="stylesheet" href={config.theme.cssUrl} /> <link rel="icon" href={config.branding.faviconUrl} /> </head> <body> <RuntimeConfigProvider config={config}> {children} </RuntimeConfigProvider> </body> </html> ); }
Vérification de droits depuis votre backend
httpPOST /api/token/authorize x-api-key: sk_live_... Content-Type: application/json { "token": "eyJ...", "checks": { "adminLevel": { "requiredLevel": "CustomerAdmin" }, "roles": { "roles": ["CONFIG"], "mode": "any" }, "appRights": { "rights": ["users.create"], "mode": "all" } } }
json{ "authorized": true, "details": { "adminLevel": { "hasLevel": true, "actualLevel": "NetworkAdmin" }, "roles": { "hasRoles": true, "matchedRoles": ["CONFIG"] }, "appRights": { "hasRights": true, "missingRights": [] } } }
Rate limiting
L'API applique trois niveaux de throttling :
| Niveau | TTL | Limite | Description |
|---|---|---|---|
| Court | 1 000 ms | 10 req | Anti-burst immédiat |
| Moyen | 60 000 ms | 100 req | Limite minute |
| Long | 3 600 000 ms | 1 000 req | Limite heure |
Certains endpoints ont des limites spécifiques (voir 08-endpoint-reference).
Réponse 429 :
json{ "statusCode": 429, "message": "Too Many Requests" }
Le header Retry-After indique la durée d'attente en secondes.
Pattern backoff exponentiel :
typescriptasync function fetchWithRetry( url: string, options: RequestInit, maxRetries = 3 ): Promise<Response> { for (let attempt = 0; attempt < maxRetries; attempt++) { const res = await fetch(url, options); if (res.status !== 429) return res; const retryAfter = parseInt(res.headers.get("Retry-After") ?? "1", 10); const delay = retryAfter * 1000 * Math.pow(2, attempt); await new Promise(r => setTimeout(r, delay)); } throw new Error("Rate limit exceeded after retries"); }
Bonnes pratiques
- Proxy serveur : ne laissez jamais le browser manipuler les cookies ou les tokens directement.
- Gestion des 401 : tenter un refresh token, puis redirect vers login. Jamais de boucle infinie.
- Préférer les WIDs : dans vos routes et vos paramètres, les WIDs (
WKST05) sont plus lisibles que les UUIDs. - Limit max 100 : ne dépassez pas
limit=100par page. Pour des exports complets, utilisez les endpoints/backup/. - TanStack Query : utilisez
staleTimepour éviter les re-fetches inutiles (recommandé : 5-30s selon la criticité). - Headers ne se cumulent pas : un appel avec
x-api-keyETAuthorization: Bearerverra la clé API prévaloir.
Pièges classiques
- Header
x-enriched-tokenabsent : toutes les requêtes frontend retournent401. Vérifier que le proxy le lit depuis le cookiewakastart_token. - Filtrage manuel inutile : ajouter
customerId=xxxmanuellement alors que le backend filtre déjà. Ça fonctionne mais c'est redondant — et ça peut casser si l'utilisateur change de Customer. limit=1000: timeout garanti sur les gros tenants. Paginez proprement.- CORS sur les appels directs : si votre frontend appelle directement l'API (sans proxy), vous aurez des erreurs CORS car le cookie
wakastart_tokenestHttpOnlyet inaccessible depuis JavaScript.
Aller plus loin
- Référence des endpoints : tableau complet de toutes les routes
- Erreurs courantes : codes HTTP et messages d'erreur détaillés
- Bonnes pratiques : patterns architecturaux avancés