Référence de l'API
Base : https://qkmvexkljdcfarulydao.supabase.co/functions/v1
Cette page est dérivée de la spécification OpenAPI, qui décrit exactement ce qui répond aujourd'hui. Le fichier brut est téléchargeable pour vos outils.
Encaissement mobile money en Côte d'Ivoire, par une intégration unique.
Conventions
- Montants : entier en unité mineure. Le franc CFA n'a pas de subdivision :
10000vaut 10 000 F. Un montant en chaîne ou à décimales est refusé — deviner l'intention d'un montant est le début des ennuis. - Idempotence :
Idempotency-Keyest obligatoire sur tout POST qui déplace de l'argent. Rejouer la même clé avec le même corps rend la réponse d'origine, sans second mouvement. - Traçabilité : toute réponse porte
LibrePay-Request-Id. C'est l'identifiant à citer au support, et celui qui apparaît dans le journal des requêtes de votre console. - Test et live : la clé détermine l'environnement. Aucun paramètre de mode, et aucune donnée ne traverse la frontière.
Le décaissement fait SORTIR de l'argent et obéit donc à deux règles que l'encaissement n'a pas : il exige une clé de portée payout, distincte de celle qui encaisse, et il est soumis à des plafonds que le marchand règle dans sa console. Au-delà d'un plafond, l'appel n'est pas refusé — il rend 202 et attend une validation humaine.
Ce qui n'existe pas encore : bénéficiaires en CRUD, règlements, pagination. Ne pas coder contre ces routes : elles ne répondent pas.
POST /v1-payments
Créer un encaissement
Crée une intention d'encaissement. La réponse ne dit pas que l'argent est arrivé : le paiement naît created, passe pending pendant que le connecteur travaille, et n'est succeeded qu'une fois la preuve reçue de l'opérateur. Écoutez le webhook, ou interrogez GET.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
amount | integer | oui | Unité mineure, entier strict. 10000 = 10 000 F. |
currency | string | non | |
operator | string | non | Opérateur visé, p. ex. orange_ci, mtn_ci. |
counterparty | object | non | Le payeur. |
merchant_reference | string | non | Votre référence. Unique par marchand : une seconde transaction portant la même valeur est refusée en 409. C'est une protection contre le double encaissement, pas une gêne. |
description | string | non | |
metadata | object | non | |
simulate | string | non | Sandbox uniquement : force l'issue sans passer par le connecteur. Ignoré en live. |
Réponses
| Statut | Description |
|---|---|
201 | Paiement créé. Un rejeu d'idempotence rend AUSSI 201. Même clé, même corps : la réponse d'origine est restituée telle quelle, statut compris, et aucun second mouvement n'a lieu. N'en déduisez donc pas qu'un paiement vient d'être créé — 201 signifie « voici le paiement qui correspond à cette clé », pas « un de plus ». Fiez-vous à l'id. |
400 | idempotency_key_required — l'en-tête est obligatoire. invalid_body — corps JSON illisible. |
401 | authentication_required — aucune clé fournie. invalid_api_key — clé inconnue, révoquée ou malformée. Les trois cas rendent le même code : les distinguer aiderait à deviner des clés valides. |
403 | merchant_inactive — le compte ne peut pas encaisser. |
409 | idempotency_in_progress — une requête identique est en cours. duplicate_reference — ce merchant_reference désigne déjà une autre transaction. |
422 | invalid_amount, invalid_currency, ou idempotency_key_reused (même clé, corps différent). amount_above_limit — le montant dépasse le plafond PAR OPÉRATION de votre compte. Ce seuil attrape la faute de frappe autant que la fraude : un montant très au-dessus de vos paiements habituels mérite d'être regardé avant d'être accepté. Si votre activité le justifie, écrivez-nous : il se relève. |
429 | Deux causes, et le code les distingue. rate_limit_exceeded — vous appelez trop vite. Voir la réponse partagée plus bas : Retry-After dit quand revenir. too_many_attempts — trop de tentatives récentes pour LE MÊME PAYEUR. Ce n'est pas une limite sur vous, c'est une protection pour lui : une rafale de tentatives sur un même numéro est la signature du test de numéros volés. Un client qui se trompe de code recommence deux ou trois fois, pas dix. Le message est volontairement identique pour tout le monde — le préciser apprendrait à qui teste des numéros la cadence à tenir pour ne plus être vu. |
500 | payment_creation_failed — une panne de notre côté, jamais une faute de votre requête. Réessayez avec la MÊME clé d'idempotence : c'est ce qui garantit qu'une opération ne partira pas deux fois. |
GET /v1-payments
Lire un paiement
L'identifiant se place en fin de chemin : …/v1-payments/txn_XXXXXXXX.
Un paiement appartenant à un autre marchand rend 404, jamais 403 : répondre 403 confirmerait son existence.
Réponses
| Statut | Description |
|---|---|
200 | Le paiement. |
404 | resource_not_found — inconnu, ou appartenant à un autre marchand. |
422 | invalid_id — l'identifiant ne ressemble pas à un txn_…. |
POST /v1-payouts
Décaisser vers un bénéficiaire
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 propres.
1. La portée de la clé. Une clé collect reçoit 403 insufficient_scope. Créez une clé dédiée au décaissement depuis votre console : la clé de votre site n'a alors aucun pouvoir d'envoi.
2. Les plafonds. Par opération et par 24 heures glissantes, réglables dans votre console. Au-delà, l'appel rend 202 et non une erreur : la demande part en validation humaine, et rien ne bouge tant qu'un responsable n'a pas tranché depuis la console. Le plafond sur 24 h compte aussi les demandes en attente — sinon il suffirait d'en ouvrir cent.
Les plafonds bornent ce qu'une clé compromise peut envoyer ; ils ne vérifient pas la destination. Gardez-les au plus près de votre usage réel.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
amount | integer | oui | Unité mineure, entier strict. 50000 = 50 000 F. |
currency | string | non | |
beneficiary_id | string | non | Un bénéficiaire de votre carnet. Ou beneficiary en ligne — l'un des deux est requis. |
beneficiary | object | non | Destination fournie en ligne. Elle est enregistrée dans votre carnet : c'est ce qui vous permet de relire ensuite, dans votre console, où votre argent est parti. |
reference | string | non | Votre référence, reprise telle quelle. |
Réponses
| Statut | Description |
|---|---|
201 | Décaissement enregistré. Comme pour un encaissement, un rejeu d'idempotence rend AUSSI 201 avec la réponse d'origine : 201 veut dire « voici le décaissement de cette clé », pas « un de plus ». |
202 | Plafond dépassé : la demande attend une validation humaine. Rien n'est parti. reason dit quel plafond (plafond_operation ou plafond_24h) et limit sa valeur. Ne traitez pas 202 comme un succès : suivez la demande par GET /v1-payouts/por_…. |
400 | idempotency_key_required, invalid_body. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée payout. C'est le cas d'une clé d'encaissement : elle ne peut pas décaisser, et c'est délibéré. |
404 | resource_not_found — bénéficiaire inconnu ou archivé. |
409 | insufficient_funds — votre solde de transfert ne couvre pas ce décaissement. Ce n'est pas votre solde de collecte : approvisionnez le transfert, ou balayez depuis la collecte. idempotency_in_progress — requête identique en cours. |
422 | invalid_amount, invalid_currency, invalid_beneficiary, idempotency_key_reused. |
429 | |
500 | payout_failed — une panne de notre côté, jamais une faute de votre requête. Réessayez avec la MÊME clé d'idempotence : c'est ce qui garantit qu'une opération ne partira pas deux fois. |
GET /v1-payouts
Lire un décaissement ou une demande
L'identifiant se place en fin de chemin. Deux formes : …/v1-payouts/txn_… pour un décaissement, …/v1-payouts/por_… pour une demande en attente de validation.
Une ressource appartenant à un autre marchand rend 404, jamais 403.
Réponses
| Statut | Description |
|---|---|
200 | Le décaissement, ou la demande. |
404 | resource_not_found. |
422 | invalid_id — ni txn_… ni por_…. |
POST /v1-payout-batches
Créer un lot de décaissements
Un lot n'est pas « n appels unitaires ». Trois différences :
- le plafond porte sur le TOTAL — sinon cent lignes sous le plafond unitaire passeraient pour dix fois le plafond journalier ;
- une ligne fautive n'annule pas les autres à l'exécution ;
- l'idempotence porte sur le lot — rejouer rend le même lot.
Le lot ne s'exécute PAS à sa création. Il naît brouillon ; l'exécution est un appel distinct (/execute). Créer et payer d'un seul geste rendrait impossible de relire ce qu'on s'apprête à envoyer — or c'est exactement ce qu'on veut relire quand il y a cent lignes.
À la création, tout est validé ou rien n'est écrit. Une ligne invalide refuse le lot entier, en donnant son rang : un lot partiellement retenu obligerait à comparer la source ligne à ligne, c'est-à-dire le travail qu'un lot évite.
Au plus 500 lignes. Au-delà, refus franc : un lot à moitié exécuté est le pire des états.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
reference | string | non | |
currency | string | non | |
lines | array | oui |
Réponses
| Statut | Description |
|---|---|
201 | Lot créé. status vaut brouillon — rien n'est parti — ou en_attente_validation si le total dépasse le plafond sur 24 h. |
400 | idempotency_key_required, invalid_body. |
403 | insufficient_scope — clé sans la portée payout. |
409 | idempotency_in_progress. |
422 | invalid_lines — tableau absent ou vide. too_many_lines — plus de 500 lignes. invalid_amount, invalid_beneficiary — le message porte le rang de la ligne fautive. idempotency_key_reused. |
GET /v1-payout-batches
Lire un lot et ses lignes
…/v1-payout-batches/pob_…. Rend le lot et le détail de ses lignes, avec le statut et le motif d'échec de chacune.
Réponses
| Statut | Description |
|---|---|
200 | Le lot et ses lignes. |
404 | resource_not_found. |
422 | invalid_id — identifiant pob_… attendu. |
POST /v1-payout-batches/{id}/execute
Exécuter un lot en brouillon
C'est cet appel qui envoie l'argent. La création n'envoie rien : un lot naît brouillon précisément pour qu'on puisse le relire avant.
La description de la création citait cette route sans que la spécification la décrive — corrigé le 2026-09-07.
Une ligne fautive n'annule pas les autres. Le lot revient avec le détail de chaque ligne et son motif d'échec ; on reprend les rangs en échec, pas le lot entier.
Exige la portée payout.
Réponses
| Statut | Description |
|---|---|
200 | Le lot exécuté, avec le détail de ses lignes. |
404 | resource_not_found — lot inconnu, ou d'un autre mode. |
409 | batch_pending_approval — le lot dépasse un plafond et attend une validation dans la console ; ou batch_not_executable — un lot déjà exécuté, annulé ou terminé ne se rejoue pas. |
422 | invalid_id — identifiant pob_… attendu. |
POST /v1-payout-batches/{id}/cancel
Annuler un lot avant exécution
N'annule que ce qui n'a rien envoyé : un lot brouillon ou en attente de validation. Un lot en cours a des lignes parties, et « annuler » laisserait croire qu'elles sont rappelées — elles ne le sont pas.
Exige la portée payout.
Réponses
| Statut | Description |
|---|---|
200 | Le lot, désormais annule. |
409 | batch_not_cancellable — lot inconnu, ou déjà exécuté. Les lignes parties ne se rappellent pas. |
422 | invalid_id — identifiant pob_… attendu. |
POST /v1-checkout
Créer une session de paiement hébergée
Quand la préférer à /v1-payments. Vous n'avez alors ni numéro à collecter, ni écran à construire, ni opérateur à choisir : vous envoyez une URL. La page est tenue à jour par nos soins, y compris quand un opérateur change son parcours.
La session naît open et expire. expires_at le dit ; passé ce délai, le lien affiche « demande expirée » plutôt qu'un formulaire mort.
Exige la portée collect — c'est un encaissement.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
amount | integer | oui | Unité mineure, entier strict. 10000 = 10 000 F. |
currency | string | non | |
description | string | non | Ce que le PAYEUR lira sur la page. Mettez-y votre référence de commande : c'est ce qui lui permet de reconnaître ce qu'il paie. |
return_url | string | non | Où renvoyer le payeur après un paiement réussi. HTTPS obligatoire — un retour en clair exposerait la référence de commande dans l'URL. |
Réponses
| Statut | Description |
|---|---|
201 | Session créée. url est ce que vous transmettez au payeur — ne reconstruisez pas ce lien vous-même, le jeton n'est pas devinable et c'est ce qui protège la session. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée collect. |
409 | La session ne peut plus recevoir de paiement : session_expired (le délai est passé), session_closed (elle est déjà payée ou fermée), lien_indisponible (le lien réutilisable a été fermé ou a expiré). Ces trois-là ne se réessaient pas : créez une nouvelle session. |
422 | invalid_amount, invalid_currency, ou invalid_return_url (l'URL de retour n'est pas en HTTPS). Sur un lien à montant libre, le payeur peut aussi recevoir amount_too_small ou amount_too_large — les bornes du lien sont rendues avec la session, affichez-les avant la saisie plutôt que de laisser découvrir un plancher après coup. Au moment où le payeur valide : invalid_msisdn (numéro ivoirien à dix chiffres attendu) et invalid_operator (opérateur hors de ceux que la session propose). |
429 | |
500 | checkout_creation_failed, payment_failed ou bornes_illisibles — une panne de notre côté, jamais une faute de votre requête. Réessayez avec la MÊME clé d'idempotence : c'est ce qui garantit qu'un encaissement ne partira pas deux fois. |
GET /v1-beneficiaries
Lire votre carnet de destinations
À quoi ça sert. /v1-settlements exige beneficiary : l'identifiant d'une destination déjà enregistrée. Cette route vous le donne, pour que vous n'ayez pas à le relever à la main dans la console.
Sans paramètre, rend vos destinations. Avec un UUID en fin de chemin (…/v1-beneficiaries/<uuid>), rend celle-là seule — une destination d'un autre marchand rend 404, jamais 403.
?active=true ne rend que les destinations actives. Par défaut on rend tout, archivées comprises : une destination archivée explique un règlement passé, et la masquer ferait chercher un fantôme.
Ce qu'on ne rend jamais
Ni l'IBAN complet, ni le numéro complet — seulement last4. Une réponse d'API se retrouve dans vos journaux, dans un cache de proxy, dans une capture d'écran de support. Quatre chiffres suffisent à reconnaître une destination qu'on a soi-même enregistrée.
Exige la portée payout : le carnet dit vers où l'argent peut sortir.
Réponses
| Statut | Description |
|---|---|
200 | Votre carnet. has_more vous évite de compter : une page pleine ne signifie pas que vous avez tout vu. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée payout. |
404 | resource_not_found — cette destination n'existe pas, ou n'est pas la vôtre. |
405 | method_not_allowed — seuls GET et POST sont acceptés. Une destination s'archive depuis la console. |
429 |
POST /v1-beneficiaries
Enregistrer une destination dans votre carnet
À quoi ça sert. Constituer votre carnet sans passer par la console, et surtout sans dépenser d'argent pour le faire.
Pourquoi cette route existe depuis le 2026-09-04
Elle n'existait pas, au motif qu'une clé compromise aurait pu s'ajouter une destination puis s'y faire régler — alors qu'il fallait, disait-on, compromettre aussi une session de console.
Ce second obstacle n'existait pas. POST /v1-payouts accepte une destination en ligne et l'enregistre au carnet, sous la même portée payout. Une clé compromise pouvait donc déjà s'en fabriquer une ; il lui en coûtait un décaissement. Refuser cette route n'empêchait rien — elle obligeait seulement l'intégrateur honnête à faire sortir de l'argent pour enregistrer un numéro.
Idempotente sans clé d'idempotence
Le carnet est unique par (marchand, mode, numéro) parmi les destinations actives. Réenvoyer la même destination ne crée pas de doublon : vous recevez 200 avec la ligne existante, contre 201 à la création. Aucun en-tête Idempotency-Key n'est requis.
Elle ne crée que des destinations de DÉCAISSEMENT. La destination de vos règlements — celle vers laquelle nous vous versons ce que nous vous devons — ne se crée pas par API : elle se désigne dans votre console, par le propriétaire du compte, avec un code de confirmation. C'est ce qui empêche une clé volée de rediriger vos règlements.
Exige la portée payout.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
msisdn | string | oui | Dix chiffres (0700000021) ou format international (+2250700000021). Normalisé avant enregistrement : les trois écritures d'un même numéro donnent une seule ligne. |
operator | string | oui | Le réseau de la destination. |
name | string | oui | Ce que vous relirez dans votre console. C'est votre libellé, pas une donnée vérifiée auprès de l'opérateur. |
Réponses
| Statut | Description |
|---|---|
200 | Cette destination était déjà à votre carnet ; elle vous est rendue telle quelle. Rien n'a été créé. |
201 | La destination a été créée. |
400 | invalid_request — corps JSON attendu. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée payout. |
422 | invalid_beneficiary — numéro illisible, operator absent, ou name de moins de deux caractères. |
429 |
GET /v1-statements
Le relevé de compte sur une période
Ce qui répond à « d'où vient mon solde ? ». Ouverture, mouvements dans l'ordre avec un solde qui court, clôture — la forme d'un relevé bancaire, faite pour être versée dans une comptabilité.
L'invariant qui rend ce document utilisable : opening_balance + total_in − total_out = closing_balance, et un relevé arrêté aujourd'hui clôture exactement sur votre solde disponible. Un relevé qui ne reproduit pas ce solde serait pire qu'aucun relevé.
Les quatre bornes portent sur la PÉRIODE, jamais sur la page. Elles ne bougent pas quand vous paginez — n'additionnez pas data pour les recalculer, vous obtiendriez un total faux dès la deuxième page.
Ce n'est pas « le relevé d'un règlement ». Un règlement est un retrait d'un montant choisi sur votre disponible, pas une clôture de période ; il figure ici comme une sortie, à sa date.
Exige la portée collect — cette route LIT, elle ne déplace rien.
Réponses
| Statut | Description |
|---|---|
200 | Le relevé. Une période sans mouvement rend les bornes à zéro et data vide — pas une erreur : « rien n'a bougé » est une réponse. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée collect. |
422 | invalid_period — from ou to absent, mal formé, ou début postérieur à la fin. Un intervalle inversé est refusé, jamais rendu vide : un relevé vide se lit « ce compte n'a pas bougé ». |
429 | |
500 | statement_failed — panne de notre côté. |
POST /v1-settlements
Demander à être réglé
Ce n'est pas un décaissement. Un décaissement (/v1-payouts) paie un TIERS depuis votre solde de transfert, que vous avez approvisionné, et il est facturé. Un règlement vous rend à VOUS ce que nous vous devons, depuis votre disponible, et il n'est pas facturé — on ne fait pas payer quelqu'un pour être remboursé.
Elle rend 202, jamais 201. Une demande est ACCEPTÉE, pas exécutée : elle attend une validation de nos équipes. Un intégrateur qui traiterait un 2xx comme « l'argent est parti » se tromperait ; le code HTTP le dit autant que le champ status.
Exige la portée payout. Cette route fait sortir de l'argent : une clé de site marchand compromise ne doit pas pouvoir vider le compte.
La destination ne se choisit pas par API (depuis le 2026-09-05)
beneficiary doit être la destination de règlement désignée par le propriétaire du compte, depuis la console, dans l'écran « Règlements ». Il n'y en a qu'une à la fois.
Ce n'est pas une formalité : POST /v1-payouts enregistre une destination à la volée, et POST /v1-beneficiaries en crée une explicitement — toutes deux sous la portée payout. Sans cette règle, une clé compromise n'aurait eu qu'à s'ajouter sa propre destination puis à appeler cette route : la portée payout aurait servi à contourner la portée payout. Un décaissement paie un tiers ; décider où le marchand se fait payer n'appartient pas à une clé.
Une destination du carnet de décaissement est donc refusée ici (destination_non_designee), et une destination fraîchement désignée attend un délai avant de servir (destination_trop_recente) — le marchand et son équipe en sont avertis par courriel, pour qu'un changement non voulu laisse le temps de réagir.
Corps de la requête
| Champ | Type | Requis | Description |
|---|---|---|---|
amount | integer | oui | Unité mineure, entier JSON strict. "50000" est refusé : sur un montant qui sort, la conversion implicite d'une chaîne est une faute, pas une commodité. |
beneficiary | string | oui | Identifiant d'une destination de votre carnet. |
Réponses
| Statut | Description |
|---|---|
202 | Demande acceptée, en attente de validation. Rien n'est encore versé. Suivez status — ou l'événement settlement.paid. |
400 | invalid_body — le corps n'est pas du JSON valide. |
401 | authentication_required, invalid_api_key. |
403 | insufficient_scope — cette clé n'a pas la portée payout. |
404 | destination_introuvable — cette destination n'est pas dans votre carnet. Enregistrez-la d'abord depuis votre console. |
409 | idempotency_key_reused (même clé, corps différent) ou idempotency_in_progress (une requête portant cette clé est en cours). |
422 | idempotency_key_required, invalid_amount, invalid_beneficiary, sous_le_plancher (montant sous le minimum de versement) ou montant_non_eligible (vous ne disposez pas de cette somme). destination_non_designee — cette destination sert aux décaissements ; celle des règlements se désigne en console. destination_trop_recente — désignée il y a moins de N heures ; le message dit à partir de quand elle servira. |
429 | |
500 | settlement_failed — une panne de notre côté, jamais une faute de votre requête. Réessayez avec la MÊME clé d'idempotence : c'est ce qui garantit qu'une demande ne sera pas déposée deux fois. |
GET /v1-settlements
Lister vos demandes, ou en lire une
Sans rien, rend vos demandes les plus récentes.
Pour en lire une seule, l'identifiant se place en fin de chemin : …/v1-settlements/stl_XXXXXXXX — comme pour les paiements. Un règlement appartenant à un autre marchand rend 404, jamais 403 : répondre 403 confirmerait son existence.
Réponses
| Statut | Description |
|---|---|
200 | La liste, ou la demande demandée. |
401 | authentication_required, invalid_api_key. |
404 | resource_not_found — cette demande n'existe pas, ou n'est pas la vôtre. |
429 |
L'objet paiement
| Champ | Type |
|---|---|
id | string |
object | string |
amount | integer |
amount_collected | integer |
fee_amount | integer |
net_amount | integer |
currency | string |
status | string |
operator | string ou null |
merchant_reference | string ou null |
failure_code | string ou null |
failure_message | string ou null |
livemode | boolean |
created_at | string |
succeeded_at | string ou null |
failed_at | string ou null |
Version d'API 2026-08-25 ·
changements ·
spécification OpenAPI