Référence API REST
Conventions générales
URL de base
http://localhost:3000 (développement)
https://[votre-domaine] (production)
Tous les endpoints sont sous le préfixe /api/.
Format des réponses
Succès :
{ "success": true, "data": { ... } }
Erreur :
{ "error": "Message d'erreur explicite" }
Codes HTTP
| Code | Signification |
|---|---|
200 |
Succès (GET, PATCH, DELETE) |
201 |
Ressource créée (POST) |
400 |
Requête invalide (champs manquants, format incorrect) |
401 |
Non authentifié |
403 |
Accès interdit (ressource d'un autre utilisateur) |
404 |
Ressource non trouvée |
429 |
Trop de requêtes (rate limiting) |
500 |
Erreur serveur interne |
Authentification
Trois modes d'authentification coexistent selon l'endpoint :
| Type | Mécanisme | Endpoints |
|---|---|---|
| Admin | Cookie auth-token (JWT HS256, 7 jours) |
/api/admin/*, /api/auth/* |
| Speaker | Code dans le body (lookup DB, session localStorage) | /api/speaker/login, /api/audio/upload |
| Public | Aucune | /api/languages, /api/languages/[id]/content, /api/statistics/track, /api/quiz/*, /api/contact, /api/health/* |
Santé (Health)
GET /api/health/live
Liveness probe — vérifie uniquement que le process Node.js répond. Utilisé par le HEALTHCHECK Docker. Ne dépend d'aucun service externe (pas de connexion BDD).
curl http://localhost:3000/api/health/live
Réponse 200 :
{ "status": "ok" }
⚠️ Cet endpoint répond toujours
200tant que Next.js tourne. Il ne vérifie PAS que PostgreSQL ou MinIO sont disponibles.
Authentification Admin
POST /api/auth/login
Authentifie un administrateur. Retourne un cookie JWT auth-token valide 7 jours.
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@racines.app","password":"motdepasse123"}' \
-c cookies.txt
Body :
{
"email": "admin@racines.app",
"password": "motdepasse123"
}
Réponse 200 :
{
"success": true,
"message": "Authentification réussie",
"admin": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "admin@racines.app"
}
}
Le cookie auth-token est positionné dans les headers Set-Cookie de la réponse (HttpOnly, Secure, SameSite=Lax).
Erreurs :
| Cas | Code | Message |
|---|---|---|
| Champs manquants | 400 | "Email et password requis" |
| Email invalide | 400 | "Email invalide" |
| Mot de passe < 8 caractères | 400 | "Le password doit avoir au moins 8 caractères" |
| Identifiants incorrects | 401 | "Email ou password incorrect" |
Note sécurité : En cas d'email inexistant ou de mauvais mot de passe, la réponse est identique (pas de fuite d'information sur l'existence du compte).
GET /api/auth/login
Vérifie si le cookie de session admin est valide. Utilisé par le frontend pour détecter une session expirée.
curl http://localhost:3000/api/auth/login \
-b cookies.txt
Réponse si authentifié :
{
"authenticated": true,
"admin": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "admin@racines.app"
}
}
Réponse si non authentifié :
{ "authenticated": false }
POST /api/auth/logout
Invalide le cookie de session admin (cookie mis à expiration immédiate).
curl -X POST http://localhost:3000/api/auth/logout \
-b cookies.txt
Réponse 200 :
{ "success": true }
Authentification Speaker
POST /api/speaker/login
Vérifie le code d'accès d'un locuteur. La session est gérée côté client dans localStorage (aucun cookie serveur).
curl -X POST http://localhost:3000/api/speaker/login \
-H "Content-Type: application/json" \
-d '{"accessCode":"RACINES-ABCDE-2025"}'
Body :
{
"accessCode": "RACINES-ABCDE-2025"
}
Le code est normalisé en majuscules côté serveur avant la vérification en base de données.
Réponse 200 :
{
"success": true,
"speaker": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Amadou Diallo",
"access_code": "RACINES-ABCDE-2025",
"language_id": "uuid-langue",
"language_code": "pul",
"language_name": "Pular"
}
}
Erreurs :
| Cas | Code | Message |
|---|---|---|
| Code manquant | 400 | "Code d'accès requis" |
| Code invalide ou speaker inactif | 401 | "Code d'accès invalide ou speaker inactif" |
Langues
GET /api/languages
Récupère la liste des langues actives. Peut être filtré par code.
# Toutes les langues actives
curl http://localhost:3000/api/languages
# Filtrer par code
curl "http://localhost:3000/api/languages?code=pul"
Query params :
| Param | Type | Description |
|---|---|---|
| code | string | Optionnel — filtre par code langue (pul, wol, etc.) |
Réponse 200 :
{
"success": true,
"languages": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Pular",
"code": "pul",
"access_code": "RACINES-XXXXX-2025",
"is_active": true,
"display_order": 1,
"primary_color": "#D97706",
"icon_url": "https://[MINIO_PUBLIC_URL]/audios/icons/pul.png",
"created_at": "2025-01-15T10:00:00Z"
}
]
}
Note :
speaker_access_codeetspeaker_namene sont PAS retournés par cet endpoint public (informations réservées aux admins).
POST /api/languages
Auth admin requise.
Crée une nouvelle langue avec son premier locuteur.
curl -X POST http://localhost:3000/api/languages \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"name": "Wolof",
"code": "wol",
"access_code": "RACINES-WOLOF-2025",
"speaker_access_code": "RACINES-SPK-WOL-2025",
"speaker_name": "Fatou Diop",
"primary_color": "#2563EB",
"is_active": false,
"display_order": 2
}'
Body :
{
"name": "Wolof",
"code": "wol",
"access_code": "RACINES-WOLOF-2025",
"speaker_access_code": "RACINES-SPK-WOL-2025",
"speaker_name": "Fatou Diop",
"primary_color": "#2563EB",
"is_active": false,
"display_order": 2
}
Réponse 201 :
{
"success": true,
"data": {
"id": "uuid-nouveau",
"name": "Wolof",
"code": "wol",
"access_code": "RACINES-WOLOF-2025",
"is_active": false,
"display_order": 2,
"primary_color": "#2563EB",
"created_at": "2026-01-15T10:00:00Z"
}
}
PATCH /api/languages/[id]
Auth admin requise.
Met à jour une ou plusieurs propriétés d'une langue. Tous les champs sont optionnels.
curl -X PATCH http://localhost:3000/api/languages/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"is_active": true}'
Body (tous les champs optionnels) :
{
"name": "Pular (Fuuta Tooro)",
"is_active": true,
"primary_color": "#B45309",
"display_order": 1,
"speaker_name": "Amadou Diallo",
"speaker_access_code": "RACINES-SPK-NEW-2025",
"icon_url": "https://[MINIO]/audios/icons/pul-v2.png"
}
Réponse 200 :
{
"success": true,
"data": { /* langue mise à jour complète */ }
}
DELETE /api/languages/[id]
Auth admin requise.
Supprime une langue et toutes ses dépendances en cascade (cartes, items, speakers, statistiques). Action irréversible. Les fichiers audio dans MinIO ne sont pas supprimés.
curl -X DELETE http://localhost:3000/api/languages/550e8400-e29b-41d4-a716-446655440000 \
-b cookies.txt
Réponse 200 :
{
"success": true,
"message": "Langue supprimée avec succès"
}
POST /api/languages/verify-code
Vérifie qu'un code d'accès joueur correspond à une langue donnée. Utilisé lors du déverrouillage d'une langue.
curl -X POST http://localhost:3000/api/languages/verify-code \
-H "Content-Type: application/json" \
-d '{"languageId":"uuid-langue","code":"RACINES-XXXXX-2025"}'
Body :
{
"languageId": "550e8400-e29b-41d4-a716-446655440000",
"code": "RACINES-XXXXX-2025"
}
Le code est normalisé en majuscules côté serveur avant la comparaison.
Réponse 200 :
{ "valid": true }
ou
{ "valid": false }
Cet endpoint ne révèle jamais si la langue existe ou non. Une langue inconnue renvoie simplement
{ "valid": false }.
GET /api/languages/[id]/content
Récupère le contenu complet d'une langue pour le téléchargement offline (IndexedDB). Retourne la langue, toutes ses cartes et tous ses items en une seule requête.
curl "http://localhost:3000/api/languages/550e8400-e29b-41d4-a716-446655440000/content"
Réponse 200 :
{
"success": true,
"language": {
"id": "uuid",
"name": "Pular",
"code": "pul",
"primary_color": "#D97706",
"icon_url": "..."
},
"cards": [
{
"id": "uuid-carte",
"language_id": "uuid",
"type": "word",
"card_number": 1,
"theme": "Famille"
}
],
"items": [
{
"id": "uuid-item",
"card_id": "uuid-carte",
"position": 1,
"original_text": "Baaba",
"translation": "Père",
"audio_url": "https://[CDN]/audios/pul/uuid-item/audio.mp3",
"lot_number": null,
"item_type": null
}
]
}
Cet endpoint est utilisé exclusivement pour le téléchargement offline. Le contenu est stocké dans IndexedDB v3.
Cartes
POST /api/cards
Auth admin requise.
Crée une nouvelle carte pour une langue.
curl -X POST http://localhost:3000/api/cards \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"language_id": "uuid-langue",
"type": "word",
"card_number": 81,
"theme": "Animaux"
}'
Body :
{
"language_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "word",
"card_number": 81,
"theme": "Animaux"
}
type doit être "word" ou "phrase".
Réponse 201 :
{
"success": true,
"data": {
"id": "uuid-nouvelle-carte",
"language_id": "uuid-langue",
"type": "word",
"card_number": 81,
"theme": "Animaux",
"created_at": "2026-01-15T10:00:00Z"
}
}
POST /api/cards/import
Auth admin requise.
Import en masse via fichier Excel (.xlsx). Crée les cartes et leurs items depuis un fichier structuré.
curl -X POST http://localhost:3000/api/cards/import \
-b cookies.txt \
-F "file=@contenu_pular.xlsx" \
-F "languageId=uuid-langue" \
-F "type=word"
Form data :
| Champ | Type | Description |
|---|---|---|
| file | File | Fichier .xlsx |
| languageId | string | UUID de la langue |
| type | string | "word" ou "phrase" |
Pour la structure exacte du fichier Excel, voir 06-import-excel.md.
Réponse 200 :
{
"success": true,
"cardsCreated": 80,
"itemsCreated": 480
}
Items
POST /api/items
Auth admin requise.
Crée un ou plusieurs items. Supporte l'insertion individuelle ou en masse.
Insertion individuelle :
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"card_id": "uuid-carte",
"position": 1,
"original_text": "Baaba",
"translation": "Père"
}'
Insertion en masse :
curl -X POST http://localhost:3000/api/items \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"items": [
{"card_id": "uuid-carte", "position": 1, "original_text": "Baaba", "translation": "Père"},
{"card_id": "uuid-carte", "position": 2, "original_text": "Yaye", "translation": "Mère"},
{"card_id": "uuid-carte", "position": 3, "original_text": "Tonton", "translation": "Oncle"},
{"card_id": "uuid-carte", "position": 4, "original_text": "Tata", "translation": "Tante"},
{"card_id": "uuid-carte", "position": 5, "original_text": "Aan", "translation": "Sœur aînée"},
{"card_id": "uuid-carte", "position": 6, "original_text": "Kaaño", "translation": "Frère cadet"}
]
}'
Champs du body :
| Champ | Type | Requis | Description |
|---|---|---|---|
| card_id | string | ✅ | UUID de la carte parente |
| position | integer | ✅ | 1–6 pour mots, 1–4 pour phrases |
| original_text | string | ✅ | Texte en langue africaine |
| translation | string | ✅ | Traduction en français |
| audio_url | string | ❌ | URL vers MinIO (null par défaut) |
| lot_number | integer | ❌ | 1 ou 2 (phrases uniquement) |
| item_type | string | ❌ | "question" ou "answer" (phrases uniquement) |
Réponse 201 (insertion en masse) :
{
"success": true,
"data": [
{
"id": "uuid-item-1",
"card_id": "uuid-carte",
"position": 1,
"original_text": "Baaba",
"translation": "Père",
"audio_url": null,
"lot_number": null,
"item_type": null,
"created_at": "2026-01-15T10:00:00Z"
}
]
}
PATCH /api/items/[id]
Auth admin requise.
Met à jour un item. Tous les champs sont optionnels.
curl -X PATCH http://localhost:3000/api/items/uuid-item \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"original_text": "Baaba (correction)", "translation": "Père (formel)"}'
Body :
{
"original_text": "Baaba (correction)",
"translation": "Père (formel)",
"audio_url": "https://[CDN]/audios/pul/uuid-item/audio.mp3"
}
Réponse 200 :
{
"success": true,
"data": { /* item mis à jour */ }
}
Locuteurs (Speakers)
POST /api/speakers
Auth admin requise.
Crée un nouveau locuteur pour une langue.
curl -X POST http://localhost:3000/api/speakers \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"language_id": "uuid-langue",
"name": "Mariama Baldé",
"access_code": "RACINES-SPK-MRB-2025",
"is_active": true
}'
Body :
{
"language_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Mariama Baldé",
"access_code": "RACINES-SPK-MRB-2025",
"is_active": true
}
Réponse 201 :
{
"success": true,
"data": {
"id": "uuid-speaker",
"language_id": "uuid-langue",
"name": "Mariama Baldé",
"access_code": "RACINES-SPK-MRB-2025",
"is_active": true,
"created_at": "2026-01-15T10:00:00Z"
}
}
Audio
POST /api/audio/upload
Auth speaker requise (code passé dans les headers ou cookie de session).
Upload un fichier audio pour un item. Le fichier est converti automatiquement en MP3 mono 96kbps via FFmpeg, puis stocké dans MinIO.
curl -X POST http://localhost:3000/api/audio/upload \
-H "X-Speaker-Code: RACINES-SPK-MRB-2025" \
-F "audio=@enregistrement.wav" \
-F "itemId=uuid-item"
Form data :
| Champ | Type | Description |
|---|---|---|
| audio | File | Fichier audio (MP3, WAV, WebM, OGG, M4A, FLAC) |
| itemId | string | UUID de l'item cible |
Limites : - Taille maximale : 50 MB - Formats acceptés : MP3, WAV, WebM, OGG, M4A, FLAC
Traitement FFmpeg automatique : - Conversion vers MP3 - Bitrate 96 kbps - Mono (1 canal) - 44 100 Hz sample rate - Normalisation du volume (EBU R128 loudnorm)
Clé MinIO générée : [code_langue]/[uuid_item]/audio.mp3
Réponse 200 :
{
"success": true,
"audioUrl": "https://[MINIO_PUBLIC_URL]/audios/pul/uuid-item/audio.mp3"
}
La colonne items.audio_url est mise à jour automatiquement en base de données.
Erreurs :
| Cas | Code | Message |
|---|---|---|
| Speaker non authentifié | 401 | "Authentification requise" |
| Fichier trop volumineux | 400 | "Fichier trop volumineux (max 50 MB)" |
| Format non supporté | 400 | "Format audio non supporté" |
| Item non trouvé | 404 | "Item non trouvé" |
GET /api/audio/serve/[[...path]]
Proxy MinIO — utilisé uniquement en développement local ou quand AUDIO_CDN_URL est absent.
curl "http://localhost:3000/api/audio/serve/pul/uuid-item/audio.mp3"
Protection path traversal : Les chemins contenant .. ou commençant par / sont rejetés avec une erreur 400.
Réponse 200 :
Stream binaire audio/mpeg depuis MinIO.
En production, les audios sont servis directement depuis le CDN MinIO (
AUDIO_CDN_URL) sans passer par ce proxy. Voir 05-audio-minio.md.
Statistiques
POST /api/statistics/track
Enregistre un événement utilisateur. Crée ou incrémente une entrée journalière dans la table statistics (upsert par date).
curl -X POST http://localhost:3000/api/statistics/track \
-H "Content-Type: application/json" \
-d '{
"eventType": "play_audio",
"languageId": "uuid-langue",
"cardId": "uuid-carte",
"itemId": "uuid-item"
}'
Body :
{
"eventType": "play_audio",
"languageId": "550e8400-e29b-41d4-a716-446655440000",
"cardId": "uuid-carte",
"itemId": "uuid-item"
}
Types d'événements :
eventType |
Déclencheur | cardId |
itemId |
|---|---|---|---|
unlock_language |
Déverrouillage d'une langue | null | null |
view_card |
Affichage d'une carte | requis | null |
play_audio |
Lecture d'un audio | requis | requis |
Réponse 200 :
{
"success": true,
"count": 42
}
count = nombre d'occurrences cumulées pour ce jour.
Comportement offline : Les événements sont mis en file d'attente dans IndexedDB (
statistics-queue) quand le réseau est indisponible, puis synchronisés automatiquement à la reconnexion.
GET /api/statistics/by-language
Auth admin requise.
Récupère les statistiques agrégées par langue pour le tableau de bord.
curl "http://localhost:3000/api/statistics/by-language?from=2026-01-01&to=2026-01-31" \
-b cookies.txt
Query params :
| Param | Type | Description |
|---|---|---|
| from | string | Format YYYY-MM-DD (optionnel) |
| to | string | Format YYYY-MM-DD (optionnel) |
Réponse 200 :
[
{
"language_id": "uuid",
"language_name": "Pular",
"language_code": "pul",
"unlock_count": 245,
"view_count": 1830,
"audio_count": 956
}
]
GET /api/statistics/top-cards
Auth admin requise.
Récupère les cartes les plus consultées.
curl "http://localhost:3000/api/statistics/top-cards?languageId=uuid&limit=10" \
-b cookies.txt
Query params :
| Param | Type | Description |
|---|---|---|
| languageId | string | Optionnel — filtre par langue |
| limit | integer | Nombre de résultats (défaut : 10) |
| from | string | Format YYYY-MM-DD (optionnel) |
| to | string | Format YYYY-MM-DD (optionnel) |
Réponse 200 :
[
{
"card_id": "uuid",
"card_number": 5,
"type": "word",
"theme": "Famille",
"language_name": "Pular",
"view_count": 312,
"audio_count": 187
}
]
Quiz (Révision QCM)
POST /api/quiz/start
Démarre une session de quiz. Crée une entrée dans quiz_sessions avec completed_at = null.
curl -X POST http://localhost:3000/api/quiz/start \
-H "Content-Type: application/json" \
-d '{
"languageId": "uuid-langue",
"categorySlug": "famille",
"categoryName": "Famille",
"totalQuestions": 15
}'
Body :
{
"languageId": "550e8400-e29b-41d4-a716-446655440000",
"categorySlug": "famille",
"categoryName": "Famille",
"totalQuestions": 15
}
totalQuestions : 15, 18 ou 20 selon le nombre d'items disponibles dans la catégorie (calculé par getQuestionCount()).
Réponse 201 :
{
"sessionId": "uuid-session"
}
POST /api/quiz/complete
Enregistre le score final d'une session de quiz. Met à jour quiz_sessions.score et completed_at.
curl -X POST http://localhost:3000/api/quiz/complete \
-H "Content-Type: application/json" \
-d '{"sessionId": "uuid-session", "score": 12}'
Body :
{
"sessionId": "uuid-session",
"score": 12
}
score = nombre de bonnes réponses (entre 0 et totalQuestions).
Réponse 200 :
{ "success": true }
Les sessions non complétées (
completed_at = null) sont considérées comme abandonnées et ne sont pas comptabilisées dans les statistiques.
Contact
POST /api/contact
Envoie un message de contact. Persiste le message en base de données, envoie un email aux admins via SMTP, et envoie une confirmation à l'expéditeur.
curl -X POST http://localhost:3000/api/contact \
-H "Content-Type: application/json" \
-d '{
"name": "Jean Dupont",
"email": "jean@example.com",
"subject": "Question sur l'\''app",
"message": "Bonjour, j'\''ai une question..."
}'
Body :
{
"name": "Jean Dupont",
"email": "jean@example.com",
"subject": "Question sur l'app",
"message": "Bonjour, j'ai une question...",
"_t": 5000
}
Champs :
| Champ | Type | Requis | Description |
|---|---|---|---|
| name | string | ✅ | Nom de l'expéditeur |
| email | string | ✅ | Email de l'expéditeur |
| subject | string | ✅ | Sujet du message |
| message | string | ✅ | Corps du message |
| website | string | ❌ | Honeypot — doit rester vide |
| _t | number | ❌ | Timestamp de début de saisie (anti-spam timing) |
Protection anti-spam (4 couches) :
- Honeypot (
website) : Si rempli, réponse200silencieuse (faux succès) - Timing minimum (
_t) : Rejet silencieux si soumission < 4 secondes - Rate limiting : Maximum 3 messages par heure par IP (via
X-Forwarded-For) - Domaines jetables : Rejet des emails issus de domaines temporaires
Réponse 200 :
{ "success": true, "message": "Emails envoyés avec succès" }
Erreurs :
| Cas | Code | Message |
|---|---|---|
| Champs manquants | 400 | "Tous les champs obligatoires doivent être remplis" |
| Domaine email jetable | 400 | "Veuillez utiliser une adresse email permanente." |
| Rate limit atteint | 429 | "Trop de messages envoyés. Veuillez réessayer dans une heure." |
| Erreur SMTP | 500 | "Erreur lors de l'envoi des emails" |
Messages de contact (Admin)
GET /api/admin/contact
Auth admin requise.
Récupère les messages de contact avec pagination et filtres.
curl "http://localhost:3000/api/admin/contact?page=1&status=unread&search=dupont" \
-b cookies.txt
Query params :
| Param | Type | Description |
|---|---|---|
| page | integer | Numéro de page (défaut : 1, 20 messages/page) |
| status | string | Filtre : unread, read, replied, archived |
| search | string | Recherche dans nom, email, sujet |
Réponse 200 :
{
"success": true,
"messages": [
{
"id": "uuid",
"name": "Jean Dupont",
"email": "jean@example.com",
"subject": "Question sur l'app",
"message": "Bonjour...",
"status": "unread",
"admin_notes": null,
"created_at": "2026-01-15T10:00:00Z"
}
],
"total": 47,
"page": 1,
"totalPages": 3
}
PATCH /api/admin/contact/[id]
Auth admin requise.
Met à jour le statut ou les notes d'un message.
curl -X PATCH http://localhost:3000/api/admin/contact/uuid-message \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"status": "replied", "admin_notes": "Répondu par email le 15/01"}'
Body :
{
"status": "replied",
"admin_notes": "Répondu par email le 15/01"
}
Valeurs de status : unread, read, replied, archived
Réponse 200 :
{ "success": true }
DELETE /api/admin/contact/[id]
Auth admin requise.
Supprime définitivement un message de contact.
curl -X DELETE http://localhost:3000/api/admin/contact/uuid-message \
-b cookies.txt
Réponse 200 :
{ "success": true }
Statistiques Admin
GET /api/admin/statistics
Auth admin requise.
Compte le nombre de lignes dans la table statistics selon des filtres.
curl "http://localhost:3000/api/admin/statistics?languageId=uuid&from=2026-01-01&to=2026-01-31" \
-b cookies.txt
Query params :
| Param | Type | Description |
|---|---|---|
| languageId | string | Optionnel — filtre par langue |
| from | string | Format YYYY-MM-DD |
| to | string | Format YYYY-MM-DD |
Réponse 200 :
{ "count": 12450 }
DELETE /api/admin/statistics
Auth admin requise.
Supprime des lignes de statistiques selon des filtres. Action irréversible.
# Supprimer les stats d'une langue sur une période
curl -X DELETE \
"http://localhost:3000/api/admin/statistics?languageId=uuid&from=2025-01-01&to=2025-12-31" \
-b cookies.txt
# Purge totale (header requis)
curl -X DELETE http://localhost:3000/api/admin/statistics \
-H "x-delete-all-confirmed: true" \
-b cookies.txt
Query params : identiques à GET /api/admin/statistics
Protection purge totale : Sans aucun filtre, le header x-delete-all-confirmed: true est obligatoire pour éviter les suppressions accidentelles.
Réponse 200 :
{ "success": true, "deleted": 12450 }
GET /api/admin/quiz/stats
Auth admin requise.
Récupère les statistiques des sessions de quiz (complètes et abandonnées).
curl "http://localhost:3000/api/admin/quiz/stats?languageId=uuid&from=2026-01-01" \
-b cookies.txt
Réponse 200 :
{
"total": 320,
"completed": 215,
"abandoned": 105,
"averageScore": 11.3,
"completionRate": 67.2,
"byCategory": [
{
"categoryName": "Famille",
"sessions": 45,
"avgScore": 12.1
}
]
}
Erreurs côté client
POST /api/client-errors
Réception des erreurs JavaScript côté client pour logging serveur. Utilisé par l'AudioPlayer pour signaler les erreurs de lecture audio.
curl -X POST http://localhost:3000/api/client-errors \
-H "Content-Type: application/json" \
-d '{"request":"HEAD https://cdn/audio.mp3","response":"403 Forbidden"}'
Body :
{
"request": "HEAD https://[CDN]/audios/pul/uuid-item/audio.mp3",
"response": "403 Forbidden"
}
Réponse 200 :
{ "ok": true }
Les erreurs sont loggées côté serveur sans données personnelles identifiables.
Tableau récapitulatif
| Endpoint | Méthode | Auth | Description |
|---|---|---|---|
/api/health/live |
GET | Aucune | Liveness probe |
/api/auth/login |
POST | — | Login admin |
/api/auth/login |
GET | Admin | Vérifier session |
/api/auth/logout |
POST | Admin | Déconnexion |
/api/speaker/login |
POST | — | Login speaker |
/api/languages |
GET | Aucune | Liste des langues actives |
/api/languages |
POST | Admin | Créer une langue |
/api/languages/[id] |
PATCH | Admin | Modifier une langue |
/api/languages/[id] |
DELETE | Admin | Supprimer (cascade) |
/api/languages/verify-code |
POST | Aucune | Vérifier code joueur |
/api/languages/[id]/content |
GET | Aucune | Contenu complet (offline) |
/api/cards |
POST | Admin | Créer une carte |
/api/cards/import |
POST | Admin | Import Excel |
/api/items |
POST | Admin | Créer un ou des items |
/api/items/[id] |
PATCH | Admin | Modifier un item |
/api/speakers |
POST | Admin | Créer un locuteur |
/api/audio/upload |
POST | Speaker | Upload audio |
/api/audio/serve/[[...path]] |
GET | Aucune | Proxy MinIO (dev) |
/api/statistics/track |
POST | Aucune | Enregistrer un événement |
/api/statistics/by-language |
GET | Admin | Stats par langue |
/api/statistics/top-cards |
GET | Admin | Top cartes |
/api/quiz/start |
POST | Aucune | Démarrer un quiz |
/api/quiz/complete |
POST | Aucune | Terminer un quiz |
/api/contact |
POST | Aucune | Envoyer un message |
/api/admin/contact |
GET | Admin | Liste messages contact |
/api/admin/contact/[id] |
PATCH | Admin | Modifier message |
/api/admin/contact/[id] |
DELETE | Admin | Supprimer message |
/api/admin/statistics |
GET | Admin | Compter stats |
/api/admin/statistics |
DELETE | Admin | Supprimer stats |
/api/admin/quiz/stats |
GET | Admin | Stats quiz |
/api/client-errors |
POST | Aucune | Erreurs JS client |