Aller au contenu principal

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​

EnvironnementURL
Productionhttps://api.caffette.app
Localhttp://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 :

JetonDurée de vieUsage
Access token720 minutes (12h)En-tête Authorization de chaque requête
Refresh token30 joursRenouvellement via POST /auth/refresh
Authorization: Bearer <access_token>
Rotation des refresh tokens

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 :

  1. À partir du claim organization_id contenu dans le JWT (cas normal).
  2. Via l'en-tête X-Tenant-Id pour 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êteRequisDescription
AuthorizationOui (routes protégées)Bearer <access_token>
Content-TypeOui (corps JSON)application/json
Idempotency-KeySelon endpointUUID v4 — évite les doublons (voir ci-dessous)
X-Provider-NameWebhooksNom 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
RouteClé d'idempotence
POST /transactions/rechargeOptionnelle (recommandée)
POST /transactions/purchaseOptionnelle (recommandée)
POST /transactions/correctionOptionnelle
POST /transactions/refundOptionnelle
POST /transactions/{id}/voidOptionnelle (défaut void:{id})
POST /reservationsObligatoire (422 si absente)
POST /reservations/{id}/statusObligatoire
POST /reservations/multi-dayObligatoire
POST /me/children/{id}/rechargeObligatoire

Pagination​

Pagination par offset​

La majorité des endpoints de liste utilisent limit + offset et renvoient :

{
"items": [ ... ],
"total": 142
}
ParamDéfautMaxDescription
limit20 (50 pour logs, 100 pour certaines listes)100–200 selon endpointNombre d'éléments
offset0—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
amountMontant brut (entier, sans suffixe _cents)
amount_formattedMontant formaté pour l'affichage (ex: "1 500 FCFA")
currencyCode ISO 4217 de la devise (ex: "XOF")

Exemple complet :

{
"amount_cents": 1500,
"amount": 1500,
"amount_formatted": "1 500 FCFA",
"currency": "XOF"
}
Devises

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 :

DomaineExemple de valeurs
Statut de carteunassigned, active, deactivated, lost
Type de transactioncredit_cash, credit_mobile_money, debit_purchase, debit_reservation, correction, refund
Statut de réservationpending, ready, collected, cancelled, refunded, uncollected
Statut de ticketopen, in_progress, waiting_on_customer, resolved, closed
Sévérité d'anomalielow, medium, high, critical
Statut d'anomalieopen, acknowledged, resolved, false_positive
Enums PascalCase

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_codeStatut HTTPDescription
Validation400 / 422Données invalides
Unauthorized401Authentification manquante ou invalide
Forbidden403Permissions insuffisantes
NotFound404Ressource inexistante
Conflict409Conflit (doublon, état incompatible)
UnprocessableEntity422Corps bien formé mais sémantiquement invalide
Internal500Erreur serveur
Erreurs de connexion

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​

CodeSignification
200 OKSuccès
201 CreatedRessource créée
204 No ContentSuccès sans corps (suppressions)
400 Bad RequestRequête malformée
401 UnauthorizedNon authentifié
403 ForbiddenAuthentifié mais non autorisé
404 Not FoundRessource introuvable
409 ConflictConflit d'état
422 Unprocessable EntityValidation échouée
500 Internal Server ErrorErreur serveur

Réponses binaires​

Certains endpoints renvoient des fichiers binaires plutôt que du JSON :

EndpointType MIMEDescription
GET /admin/org/cards/{id}/qrimage/pngQR code d'une carte
GET /admin/org/cards/{id}/cardimage/pngCarte formatée (350×500)
POST /admin/org/cards/qr-export/zipapplication/zipArchive de QR codes
POST /admin/org/cards/cards-export/zipapplication/zipArchive de cartes formatées
GET /admin/org/*/exporttext/csv / XLSXExports 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​