Sommaire · Conventions de l’API

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.

SituationRéponse
Même clé, même corpsLa réponse d’origine est rejouée, avec l’en-tête Idempotent-Replayed: true. Aucune nouvelle opération.
Même clé, corps différent409 IDEMPOTENCY_KEY_REUSED
Même clé, première requête encore en cours409 IDEMPOTENCY_REQUEST_IN_PROGRESS et Retry-After : réessayez avec la même clé.
Clé absente ou mal formée400 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ême Idempotency-Key : Galsen Pay répondra par le résultat de la première, sans jamais la doubler.
Node.js
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
"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 reference lisible (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.