Conventions de l'API
Cette page décrit les règles communes à tous les endpoints de l'API Caffette : authentification, en-têtes, pagination, format monétaire, énumérations, idempotence et gestion des erreurs.
URL de base
| Environnement | URL |
|---|---|
| Production | https://api.caffette.app |
| Local | http://localhost:8080 |
Toutes les routes métier sont préfixées par /api/v1.
Authentification
L'API utilise des JWT (JSON Web Tokens) signés en HMAC-SHA256. Deux types de jetons existent :
| Jeton | Durée de vie | Usage |
|---|---|---|
| Access token | 720 minutes (12h) | En-tête Authorization de chaque requête |
| Refresh token | 30 jours | Renouvellement via POST /auth/refresh |
Authorization: Bearer <access_token>
Chaque POST /auth/refresh invalide l'ancien refresh token et en émet un nouveau. Si un token inconnu est présenté (vol potentiel), toutes les sessions de l'utilisateur sont révoquées.
Contexte tenant
Les routes /admin/org/* et la plupart des routes métier nécessitent un contexte tenant. Celui-ci est résolu automatiquement de deux façons :
- À partir du claim
organization_idcontenu dans le JWT (cas normal). - Via l'en-tête
X-Tenant-Idpour les opérations explicites sur une organisation donnée.
Le middleware Tenant positionne le search_path PostgreSQL sur tenant_{slug} avant chaque requête, garantissant l'isolation des données.
En-têtes
| En-tête | Requis | Description |
|---|---|---|
Authorization | Oui (routes protégées) | Bearer <access_token> |
Content-Type | Oui (corps JSON) | application/json |
Idempotency-Key | Selon endpoint | UUID v4 — évite les doublons (voir ci-dessous) |
X-Provider-Name | Webhooks | Nom du fournisseur de paiement (fedapay) |
Idempotence
Les opérations financières (recharges, achats, corrections, remboursements, réservations) acceptent un en-tête Idempotency-Key. Si la même clé est renvoyée :
- La deuxième requête ne ré-applique pas l'opération.
- La réponse de la première requête est renvoyée telle quelle.
POST /api/v1/transactions/recharge
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
| Route | Clé d'idempotence |
|---|---|
POST /transactions/recharge | Optionnelle (recommandée) |
POST /transactions/purchase | Optionnelle (recommandée) |
POST /transactions/correction | Optionnelle |
POST /transactions/refund | Optionnelle |
POST /transactions/{id}/void | Optionnelle (défaut void:{id}) |
POST /reservations | Obligatoire (422 si absente) |
POST /reservations/{id}/status | Obligatoire |
POST /reservations/multi-day | Obligatoire |
POST /me/children/{id}/recharge | Obligatoire |
Pagination
Pagination par offset
La majorité des endpoints de liste utilisent limit + offset et renvoient :
{
"items": [ ... ],
"total": 142
}
| Param | Défaut | Max | Description |
|---|---|---|---|
limit | 20 (50 pour logs, 100 pour certaines listes) | 100–200 selon endpoint | Nombre d'éléments |
offset | 0 | — | Décalage |
Pagination par curseur
Les transactions (GET /transactions) utilisent une pagination par curseur en plus de l'offset :
{
"items": [ ... ],
"total": 142,
"next_cursor": "2025-06-01T10:00:00+00:00,550e8400-e29b-41d4-a716-446655440000",
"currency": "XOF"
}
Le curseur a le format {timestamp_rfc3339},{dernier_id}. Passez-le dans ?cursor=... pour obtenir la page suivante. next_cursor vaut null sur la dernière page.
Format monétaire
Tous les montants sont stockés et transmis en unités mineures (centimes) sous forme d'entiers, dans le champ suffixé _cents.
{
"amount_cents": 1500
}
Enrichissement monetize
De nombreux endpoints enrichissent les objets monétaires avec des champs dérivés :
| Champ ajouté | Description |
|---|---|
amount | Montant brut (entier, sans suffixe _cents) |
amount_formatted | Montant formaté pour l'affichage (ex: "1 500 FCFA") |
currency | Code ISO 4217 de la devise (ex: "XOF") |
Exemple complet :
{
"amount_cents": 1500,
"amount": 1500,
"amount_formatted": "1 500 FCFA",
"currency": "XOF"
}
La devise par défaut est le XOF (Franc CFA), dont l'exposant est 0 : 1500 centimes = 1500 XOF = "1 500 FCFA". La devise est configurable par organisation.
Énumérations
Les énumérations sont sérialisées en snake_case sauf mention contraire :
| Domaine | Exemple de valeurs |
|---|---|
| Statut de carte | unassigned, active, deactivated, lost |
| Type de transaction | credit_cash, credit_mobile_money, debit_purchase, debit_reservation, correction, refund |
| Statut de réservation | pending, ready, collected, cancelled, refunded, uncollected |
| Statut de ticket | open, in_progress, waiting_on_customer, resolved, closed |
| Sévérité d'anomalie | low, medium, high, critical |
| Statut d'anomalie | open, acknowledged, resolved, false_positive |
Certaines énumérations n'ont pas de renommage et sont sérialisées en PascalCase : meal_type ("Vegetarian", "Meat", "Fish"), kind de point de vente ("Fixed", "Mobile"), kind de catégorie ("Buvette", "ReservationMeal", "ReservationSnackComponent"), channel de notification ("Email", "Sms", "WhatsApp", "Webhook"). Vérifiez la casse exacte dans chaque endpoint.
Erreurs
Les erreurs renvoient un objet JSON avec un code machine et un message :
{
"error_code": "Validation",
"message": "Le montant doit être supérieur à 0"
}
Codes d'erreur
error_code | Statut HTTP | Description |
|---|---|---|
Validation | 400 / 422 | Données invalides |
Unauthorized | 401 | Authentification manquante ou invalide |
Forbidden | 403 | Permissions insuffisantes |
NotFound | 404 | Ressource inexistante |
Conflict | 409 | Conflit (doublon, état incompatible) |
UnprocessableEntity | 422 | Corps bien formé mais sémantiquement invalide |
Internal | 500 | Erreur serveur |
Les erreurs 401 du endpoint POST /auth/login sont normalisées en AUTH_INVALID_CREDENTIALS pour faciliter la gestion côté frontend. Les autres erreurs (compte désactivé, organisation suspendue) sont propagées telles quelles.
Codes HTTP
| Code | Signification |
|---|---|
200 OK | Succès |
201 Created | Ressource créée |
204 No Content | Succès sans corps (suppressions) |
400 Bad Request | Requête malformée |
401 Unauthorized | Non authentifié |
403 Forbidden | Authentifié mais non autorisé |
404 Not Found | Ressource introuvable |
409 Conflict | Conflit d'état |
422 Unprocessable Entity | Validation échouée |
500 Internal Server Error | Erreur serveur |
Réponses binaires
Certains endpoints renvoient des fichiers binaires plutôt que du JSON :
| Endpoint | Type MIME | Description |
|---|---|---|
GET /admin/org/cards/{id}/qr | image/png | QR code d'une carte |
GET /admin/org/cards/{id}/card | image/png | Carte formatée (350×500) |
POST /admin/org/cards/qr-export/zip | application/zip | Archive de QR codes |
POST /admin/org/cards/cards-export/zip | application/zip | Archive de cartes formatées |
GET /admin/org/*/export | text/csv / XLSX | Exports de données |
Ces réponses incluent un en-tête Content-Disposition: attachment; filename="...".
Santé du service
GET /healthz
Sans authentification. Retourne :
{
"status": "ok",
"service": "caffette-api",
"version": "1.2.0"
}
Prochaines étapes
- Authentification — connexion, rafraîchissement, déconnexion
- Permissions — modèle de permissions granulaire
- Gestion des erreurs — bonnes pratiques de gestion