Conventions de l’API
API REST en JSON sur https. Adresse de base : https://backend.v1.pay.galsen.tech. Toutes les routes marchand commencent par /v1.
Enveloppe des réponses
Chaque réponse JSON a la même forme, qu’elle réussisse ou non :
{ "success": true, "data": { … }, "meta": { … }, "requestId": "…" }Fiez-vous à error.code (stable) et non au texte de error.message (qui peut changer). Le requestId, aussi présent dans l’en-tête X-Request-ID, identifie la requête : donnez-le au support en cas de problème. Voir Erreurs.
Montants
- Toujours des entiers en unités mineures. Le franc CFA (
XOF) n’a pas de centimes :10000= 10 000 F CFA. - Un nombre décimal (
100.5) est refusé (400 VALIDATION_ERROR). Un entier peut aussi être envoyé sous forme de chaîne de chiffres ("10000"). - En réponse, un montant est un nombre JSON, ou une chaîne s’il dépasse 253 (aucune perte de précision).
- Seule devise acceptée aujourd’hui :
XOF.
Idempotence : jamais d’opération en double
Sur les opérations d’argent (POST /v1/payment-intents, POST /v1/payments, POST /v1/refunds, POST /v1/payouts), l’en-tête Idempotency-Key est obligatoire. Générez une valeur unique par opération métier (un UUID v4 est recommandé ; 8 à 255 caractères parmi lettres, chiffres, _ . : -) et réutilisez-la si vous renvoyez la même opération après une coupure.
| Situation | Réponse |
|---|---|
| Même clé, même corps | La réponse d’origine est rejouée, avec l’en-tête Idempotent-Replayed: true. Aucune nouvelle opération. |
| Même clé, corps différent | 409 IDEMPOTENCY_KEY_REUSED |
| Même clé, première requête encore en cours | 409 IDEMPOTENCY_REQUEST_IN_PROGRESS et Retry-After : réessayez avec la même clé. |
| Clé absente ou mal formée | 400 INVALID_IDEMPOTENCY_KEY |
Une clé vit 72 heures et ne vaut que pour votre compte marchand.
Délai dépassé : renvoyez, ne recréez pas
Si votre requête échoue sur un délai ou une coupure réseau, vous ne savez pas si l’opération a eu lieu. Renvoyez-la avec la mêmeIdempotency-Key : Galsen Pay répondra par le résultat de la première, sans jamais la doubler.import { randomUUID } from 'node:crypto'
// La clé est attachée à la commande : un nouvel essai réutilise la même.
const cle = commande.cleIdempotence ?? (commande.cleIdempotence = randomUUID())
await fetch('https://backend.v1.pay.galsen.tech/v1/payment-intents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GALSEN_SECRET_KEY}`,
'Idempotency-Key': cle,
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount: commande.total, currency: 'XOF', provider: 'WAVE' }),
})Pagination
Les listes sont triées de la plus récente à la plus ancienne et paginées par curseur : ?limit=1..100 (25 par défaut) et ?cursor= avec la valeur meta.nextCursor de la page précédente.
"meta": { "limit": 25, "hasMore": true, "nextCursor": "eyJjcmVhdGVkQXQiOi…" }Quand hasMore vaut false, vous avez tout lu. Un curseur invalide répond 422 INVALID_CURSOR.
Limites de débit
Au-delà de la limite, l’API répond 429 RATE_LIMIT_EXCEEDED avec un en-tête Retry-After (en secondes) : attendez ce délai avant de réessayer. Les en-têtes X-RateLimit-Limit et X-RateLimit-Remaining accompagnent les réponses. Valeurs actuelles, par clé et par minute : 600 requêtes en général, 120 sur les opérations d’argent. Elles peuvent évoluer : lisez les en-têtes plutôt que de les coder en dur.
Dates et identifiants
- Dates en ISO 8601, en UTC (
2026-11-09T14:05:00.000Z) — l’heure de Dakar est l’UTC. - Identifiants en UUID. Un paiement a aussi une
referencelisible (GSP-+ 12 caractères), utile pour le support. - Une ressource d’un autre marchand répond
404, exactement comme une ressource qui n’existe pas.
Champs inconnus
Un champ non documenté dans un corps de requête est refusé (400 VALIDATION_ERROR, avec la liste des champs en cause dans error.details.fields). Cela protège contre les fautes de frappe silencieuses.