Décaissements
Envoyer de l'argent à quelqu'un — un fournisseur, un coursier, un affilié — depuis votre solde de transfert.
C'est la seule route de cette API qui fait sortir de l'argent vers une destination que vous choisissez. Elle obéit donc à deux règles que l'encaissement n'a pas.
1. Il vous faut une clé de décaissement
Une clé d'encaissement ne décaisse pas. Elle reçoit 403 insufficient_scope, et c'est délibéré : la clé qui vit dans votre site marchand — celle qui a le plus de chances de fuiter — n'a aucun pouvoir d'envoi.
Créez une clé dédiée depuis votre console, onglet Compte, en choisissant le service Décaissement. Gardez-la sur un serveur qui ne sert qu'à ça, et révoquez-la seule si vous la soupçonnez.
2. Les plafonds ne refusent pas, ils font attendre
Deux plafonds, réglables dans votre console (onglet Décaissements) :
| Plafond | Défaut | Ce qu'il borne |
|---|---|---|
| Par opération | 100 000 F | Un seul envoi |
| Par 24 heures | 500 000 F | Le total glissant, demandes en attente comprises |
Au-delà, l'appel ne renvoie pas d'erreur : il rend 202 et la demande part en validation dans votre console. Refuser casserait une paie légitime le jour où elle dépasse ; laisser passer viderait le compte. Un responsable tranche, avec un code reçu par courriel — le même contrôle que pour un décaissement lancé à la main.
Le plafond sur 24 h compte aussi ce qui attend : sans cela, il suffirait d'ouvrir cent demandes sous le plafond.
Les plafonds bornent la perte, ils ne l'empêchent pas. Ils ne vérifient pas la destination : une clé compromise peut envoyer vers n'importe quel numéro, jusqu'au plafond. Gardez-les au plus près de votre usage réel.
Envoyer
POST /v1-payouts
Authorization: Bearer vp_sk_test_…
Idempotency-Key: SAL-08-2026-017
{
"amount": 50000,
"currency": "XOF",
"beneficiary": { "msisdn": "0700000021", "operator": "orange_ci", "name": "Koffi Y." },
"reference": "SAL-08-2026-017"
}
La destination fournie à l'appel est enregistrée dans votre carnet : c'est ce qui vous permet de relire ensuite, dans votre console, où votre argent est parti. Vous pouvez aussi viser un bénéficiaire existant avec beneficiary_id.
La réponse dit laquelle des deux choses s'est produite
// 201 — c'est parti
{ "id": "txn_…", "object": "payout", "amount": 50000,
"fee_amount": 250, "total_debited": 50250, "status": "created" }
// 202 — retenu, rien n'est parti
{ "id": "por_…", "object": "payout_request", "status": "pending_approval",
"reason": "plafond_operation", "limit": 100000 }
Ne traitez pas tout 2xx comme un succès. Un 202 noté « payé » dans votre système, c'est un salaire que vous croyez avoir versé. Testez object, ou utilisez le SDK :
const r = await valsoria.creerDecaissement({ amount: 50000, beneficiary: {…} });
if (LibrePay.enAttenteDeValidation(r)) {
// r.reason dit quel plafond, r.limit sa valeur.
}
Suivre
GET /v1-payouts/txn_… pour un décaissement, GET /v1-payouts/por_… pour une demande retenue. Une ressource d'un autre marchand rend 404, jamais 403 : répondre 403 confirmerait son existence.
Les états : created → processing → succeeded ou failed. Et indeterminate — l'opérateur n'a pas confirmé. Nous ne rejouons jamais un envoi incertain, et vous ne devriez pas non plus : nous vérifions auprès de l'opérateur avant de conclure.
Envoyer en masse
Un lot n'est pas « n appels unitaires ». Trois différences, et chacune vous évite un ennui :
- le plafond porte sur le TOTAL — sinon cent lignes sous le plafond unitaire passeraient pour dix fois votre plafond journalier ;
- une ligne fautive n'annule pas les autres — un numéro invalide en ligne 47 ne prive pas de salaire les 46 précédentes ;
- l'idempotence porte sur le lot — rejouer la création rend le même lot.
Créer, puis exécuter — en deux gestes
POST /v1-payout-batches
Idempotency-Key: PAIE-2026-08
{ "reference": "PAIE-2026-08", "lines": [
{ "amount": 30000, "beneficiary": { "msisdn": "0700000021", "operator": "orange_ci", "name": "Koffi Y." } },
{ "amount": 40000, "beneficiary_id": "…", "reference": "SAL-002" }
] }
Le lot naît brouillon : rien n'est parti. L'exécution est un appel distinct :
POST /v1-payout-batches/pob_…/execute
C'est délibéré : on relit ce qu'on s'apprête à envoyer avant de l'envoyer, ce qui n'a de sens que si les deux gestes sont séparés. POST …/cancel annule tant que rien n'est parti.
À la création, tout est valide ou rien n'est écrit
Une ligne invalide refuse le lot entier — et le message porte le rang :
{ "error": { "code": "invalid_beneficiary",
"message": "Ligne 47 : beneficiary.msisdn attendu : 10 chiffres…" } }
Un lot partiellement retenu vous obligerait à comparer votre fichier ligne à ligne à ce qui a été gardé — exactement le travail qu'un lot évite. Au plus 500 lignes par lot.
termine_avec_erreurs n'est pas termine
L'exécution rend 200 dans les deux cas. Vérifiez status. Une ligne qui échoue porte son failure_code et son failure_message ; les autres sont parties.
const lot = await valsoria.executerLot('pob_…');
const aReprendre = LibrePay.lignesEnEchec(lot); // rangs + motifs
Si le total dépasse votre plafond sur 24 h, le lot naît en_attente_validation et refuse de s'exécuter (409) tant qu'un responsable ne l'a pas validé depuis la console, avec un code reçu par courriel.
Le carnet de bénéficiaires, par API
GET /v1-beneficiaries liste votre carnet, POST /v1-beneficiaries y ajoute une destination. Les deux exigent la portée payout.
La liste est paginée : limit (50 par défaut, 100 au maximum), starting_after, et une réponse qui porte total et has_more — vous n'avez donc pas à compter vous-même pour savoir s'il reste des pages.
GET /v1-beneficiaries?limit=50
→ { "data": [...], "total": 128, "limit": 50, "has_more": true }
La création est idempotente sans clé d'idempotence : le carnet est unique par (marchand, mode, coordonnée). Rejouer le même appel rend la même ligne avec un 200 au lieu d'un 201 — pas un doublon.
Ce qui manque encore
Modifier ou archiver une destination par API. On lit et on ajoute ; on ne change ni ne retire depuis l'API. Un décaissement passé doit rester lisible avec la destination qu'il visait vraiment, et une coordonnée qu'on peut réécrire à distance est exactement ce qu'une clé volée réécrirait. Les deux gestes existent dans votre console.
La destination de vos RÈGLEMENTS ne se crée pas ici — ni par cette route, ni par aucune autre. C'est le compte sur lequel nous vous versons : il se désigne dans votre console, par le propriétaire du compte, avec un code de confirmation et un délai avant première utilisation. Voir l'accueil.
Version d'API 2026-08-25 ·
changements ·
spécification OpenAPI