WakaStart

Utilisation de l'API

Authentifier les requêtes, gérer la pagination, le multi-tenant, la runtime config et les conventions REST.

Version v1.07 min de lecture

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 :

http
Authorization: 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.

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

ConventionDétail
Verbes HTTPGET (lecture), POST (création), PATCH (mise à jour partielle), PUT (remplacement), DELETE (suppression)
Format dateISO 8601 avec timezone : 2026-05-19T10:30:00.000Z
Identifiants dans les URLsPréférer les WIDs (/config/users/WKST05) aux UUIDs
Content-Typeapplication/json sur tous les POST/PATCH/PUT
EncodageUTF-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 :

http
GET /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 :

ParamDéfautMaxDescription
page1Numéro de page (1-indexed)
limit20100Nombre d'éléments par page

Pattern avec TanStack Query :

typescript
import { 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 :

http
GET /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 :

adminLevelPortée des données retournées
WakaAdminToute la plateforme
OwnerAdminSon Partner entier
NetworkAdminSon Network + tous ses Customers
CustomerAdminSon Customer uniquement
UserSon 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 :

http
GET /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

http
POST /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 :

NiveauTTLLimiteDescription
Court1 000 ms10 reqAnti-burst immédiat
Moyen60 000 ms100 reqLimite minute
Long3 600 000 ms1 000 reqLimite 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 :

typescript
async 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=100 par page. Pour des exports complets, utilisez les endpoints /backup/.
  • TanStack Query : utilisez staleTime pour éviter les re-fetches inutiles (recommandé : 5-30s selon la criticité).
  • Headers ne se cumulent pas : un appel avec x-api-key ET Authorization: Bearer verra la clé API prévaloir.

Pièges classiques

  • Header x-enriched-token absent : toutes les requêtes frontend retournent 401. Vérifier que le proxy le lit depuis le cookie wakastart_token.
  • Filtrage manuel inutile : ajouter customerId=xxx manuellement 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_token est HttpOnly et inaccessible depuis JavaScript.

Aller plus loin

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