Obtenir un devis
curl --request POST \
--url https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
--header 'X-API-Key: <api-key>'import requests
url = "https://sandbox-api.caurisflux.com/api/v1/payouts/quote"
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
fetch('https://sandbox-api.caurisflux.com/api/v1/payouts/quote', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandbox-api.caurisflux.com/api/v1/payouts/quote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sandbox-api.caurisflux.com/api/v1/payouts/quote"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandbox-api.caurisflux.com/api/v1/payouts/quote")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox-api.caurisflux.com/api/v1/payouts/quote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyPayouts (Décaissements)
Obtenir un devis
Calculer les frais et taux avant de créer un payout
POST
/
payouts
/
quote
Obtenir un devis
curl --request POST \
--url https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
--header 'X-API-Key: <api-key>'import requests
url = "https://sandbox-api.caurisflux.com/api/v1/payouts/quote"
headers = {"X-API-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
fetch('https://sandbox-api.caurisflux.com/api/v1/payouts/quote', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandbox-api.caurisflux.com/api/v1/payouts/quote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sandbox-api.caurisflux.com/api/v1/payouts/quote"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandbox-api.caurisflux.com/api/v1/payouts/quote")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandbox-api.caurisflux.com/api/v1/payouts/quote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyObtient un devis pour un payout avec taux de change, frais et montant total à débiter.
Le devis est valide 5 minutes. Utilisez le
La parité est toujours privilégiée quand les deux options sont possibles.
Lorsqu’un complément a lieu, la réponse porte un objet
Objet
Objet
quoteId retourné dans POST /payouts/initiate pour confirmer.
Authentification
X-API-Key: pk_test_xxx:sk_test_xxx
Flux recommandé
1
Obtenir un devis
POST /payouts/quote → Recevez le quoteId, les frais et le montant total2
Afficher au marchand
Montrez les frais, taux de change et montant total avant confirmation
3
Confirmer le payout
POST /payouts/initiate avec le quoteId + détails du bénéficiaireParamètres
| Champ | Type | Requis | Description |
|---|---|---|---|
amount | number | Oui | Montant à envoyer |
currency | string | Oui | Devise du montant (XOF, USD, EUR…) |
destinationCountry | string | Oui | Code ISO du pays destination |
destinationCurrency | string | Non | Devise de destination (si différente) |
method | string | Oui | Méthode : wave, orange_money, bank_transfer, etc. |
type | string | Non | Type de transfert : c2c ou b2b (défaut: c2c) |
allowFxFrom | string | Non | Autorise le complément depuis cette devise si le portefeuille débité ne couvre pas — voir Conversion de portefeuille |
Le montant peut être dans n’importe quelle devise. Le système calcule automatiquement les conversions.
Quel portefeuille est débité ?
Le devis choisit le portefeuille à débiter dans cet ordre :- la devise du payout (
currency), si vous détenez ce portefeuille et qu’il couvre ; - à défaut, la devise de règlement de votre compte.
totalDebit.currency vous indique le portefeuille effectivement retenu — ce n’est pas
nécessairement la devise de règlement de votre compte.
Les frais suivent une règle distincte : le barème est toujours libellé en XOF, puis
converti dans la devise du portefeuille débité. fees.billed conserve le montant du barème
et le taux appliqué, pour que le calcul reste vérifiable.
Conversion de portefeuille
Quand aucun portefeuille ne couvre seul le débit, CaurisFlux peut compléter le manque depuis une autre devise. Seul le manque est converti, jamais le portefeuille entier. Deux régimes s’appliquent.Parité fixe — automatique
Les paires à parité 1:1 (XOF ↔ XAF) sont converties sans consentement : sans spread,
l’opération est sans perte pour vous.
Paire flottante — consentement requis
Les paires soumises à un taux du jour (XOF ↔ CDF, USD ↔ XOF…) coûtent un spread. Elles
ne sont jamais converties implicitement : il faut passer
allowFxFrom.fxTopUp :
"fxTopUp": {
"fromCurrency": "CDF",
"toCurrency": "XOF",
"sourceAmount": 2510,
"targetAmount": 502,
"rate": 0.2,
"spreadPercent": 1.5,
"parity": false
}
| Champ | Type | Description |
|---|---|---|
fromCurrency | string | Portefeuille ponctionné pour le complément |
toCurrency | string | Portefeuille alimenté (celui qui est débité) |
sourceAmount | number | Montant prélevé sur fromCurrency |
targetAmount | number | Montant crédité en toCurrency |
rate | number | Taux appliqué |
spreadPercent | number | Marge appliquée — 0 en parité |
parity | boolean | true si la conversion est à parité fixe 1:1 |
fxTopUp décrit la conversion de vos portefeuilles. Il ne faut pas le confondre avec
fx, qui décrit la conversion entre la devise envoyée et la devise reçue par le bénéficiaire.
Les deux peuvent être présents simultanément.Exemples
Mobile Money (local)
curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"currency": "XOF",
"destinationCountry": "SN",
"method": "wave"
}'
200 - Réponse
{
"quoteId": "QT-MOBILE-001",
"expiresAt": "2026-04-03T15:00:00.000Z",
"expiresIn": 300,
"input": { "amount": 50000, "currency": "XOF" },
"source": { "amount": 50000, "currency": "XOF" },
"destination": { "amount": 50000, "currency": "XOF", "country": "SN" },
"fees": {
"amount": 750,
"currency": "XOF",
"billed": { "amount": 750, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 50750, "currency": "XOF" },
"fxTopUp": null,
"fx": null,
"method": "wave"
}
Virement Bancaire (local)
curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"currency": "XOF",
"destinationCountry": "SN",
"method": "bank_transfer"
}'
200 - Réponse
{
"quoteId": "QT-BANK-001",
"expiresAt": "2026-04-03T15:00:00.000Z",
"expiresIn": 300,
"input": { "amount": 500000, "currency": "XOF" },
"source": { "amount": 500000, "currency": "XOF" },
"destination": { "amount": 500000, "currency": "XOF", "country": "SN" },
"fees": {
"amount": 2500,
"currency": "XOF",
"billed": { "amount": 2500, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 502500, "currency": "XOF" },
"fxTopUp": null,
"fx": null,
"method": "bank_transfer"
}
Payout XAF financé par un portefeuille XOF (parité)
Le marchand demande un payout en XAF mais ne détient que 900 XAF. Le manque est complété depuis le XOF, à parité 1:1 — donc sans consentement ni spread.curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XAF",
"destinationCountry": "CM",
"method": "mtn_money"
}'
200 - Réponse
{
"quoteId": "QT7F3A9C21B4D8",
"expiresAt": "2026-08-11T15:00:00.000Z",
"expiresIn": 300,
"input": { "amount": 5000, "currency": "XAF" },
"source": { "amount": 5000, "currency": "XAF" },
"destination": { "amount": 5000, "currency": "XAF", "country": "CM" },
"fees": {
"amount": 200,
"currency": "XAF",
"billed": { "amount": 200, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 5200, "currency": "XAF" },
"fxTopUp": {
"fromCurrency": "XOF",
"toCurrency": "XAF",
"sourceAmount": 4300,
"targetAmount": 4300,
"rate": 1,
"spreadPercent": 0,
"parity": true
},
"fx": null,
"method": "mtn_money"
}
Payout XOF financé par un portefeuille CDF (consentement)
Ici la paire est flottante. SansallowFxFrom, le devis échoue en
INSUFFICIENT_BALANCE.
curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 600,
"currency": "XOF",
"destinationCountry": "SN",
"method": "wave",
"allowFxFrom": "CDF"
}'
200 - Réponse
{
"quoteId": "QT2E8B77C1A0F5",
"expiresAt": "2026-08-11T15:00:00.000Z",
"expiresIn": 300,
"input": { "amount": 600, "currency": "XOF" },
"source": { "amount": 600, "currency": "XOF" },
"destination": { "amount": 600, "currency": "XOF", "country": "SN" },
"fees": {
"amount": 32,
"currency": "XOF",
"billed": { "amount": 32, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 632, "currency": "XOF" },
"fxTopUp": {
"fromCurrency": "CDF",
"toCurrency": "XOF",
"sourceAmount": 2510,
"targetAmount": 502,
"rate": 0.2,
"spreadPercent": 1.5,
"parity": false
},
"fx": null,
"method": "wave"
}
sourceAmount est arrondi au supérieur : un arrondi au plus proche pourrait laisser le
complément sous le montant manquant et faire échouer le débit.International C2C (USD → INR)
curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 500,
"currency": "USD",
"destinationCountry": "IN",
"destinationCurrency": "INR",
"method": "bank_transfer",
"type": "c2c"
}'
200 - Réponse
{
"quoteId": "QT-A1B2C3D4E5F6",
"expiresAt": "2026-04-03T13:20:00.000Z",
"expiresIn": 300,
"input": { "amount": 500, "currency": "USD" },
"source": { "amount": 500, "currency": "USD" },
"destination": { "amount": 42170, "currency": "INR", "country": "IN" },
"fees": {
"amount": 7583,
"currency": "XOF",
"billed": { "amount": 7583, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 313083, "currency": "XOF" },
"fxTopUp": null,
"fx": {
"rate": 84.34,
"spreadPercent": 1,
"sourceCurrency": "USD",
"destinationCurrency": "INR"
},
"method": "bank_transfer"
}
B2B SWIFT (USD → EUR)
curl -X POST https://sandbox-api.caurisflux.com/api/v1/payouts/quote \
-H "X-API-Key: pk_test_xxx:sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "USD",
"destinationCountry": "DE",
"destinationCurrency": "EUR",
"method": "bank_transfer",
"type": "b2b"
}'
200 - Réponse
{
"quoteId": "QT-B2B-001",
"expiresAt": "2026-04-03T15:00:00.000Z",
"expiresIn": 300,
"input": { "amount": 5000, "currency": "USD" },
"source": { "amount": 5000, "currency": "USD" },
"destination": { "amount": 4580, "currency": "EUR", "country": "DE" },
"fees": {
"amount": 15000,
"currency": "XOF",
"billed": { "amount": 15000, "currency": "XOF", "rate": 1 }
},
"totalDebit": { "amount": 3070000, "currency": "XOF" },
"fxTopUp": null,
"fx": {
"rate": 0.916,
"spreadPercent": 1,
"sourceCurrency": "USD",
"destinationCurrency": "EUR"
},
"method": "bank_transfer"
}
Champs de réponse
| Champ | Type | Description |
|---|---|---|
quoteId | string | ID du devis à utiliser dans /payouts/initiate |
expiresAt | string | Date d’expiration du devis |
expiresIn | number | Secondes restantes avant expiration |
input | object | Montant et devise demandés |
source | object | Montant envoyé au provider |
destination | object | Montant reçu par le bénéficiaire |
fees | object | Frais appliqués, dans la devise débitée |
fees.billed | object | Barème d’origine en XOF et taux de conversion appliqué |
totalDebit | object | Montant total débité, dans la devise du portefeuille retenu |
fxTopUp | object|null | Complément de portefeuille appliqué (null si aucun) |
fx | object|null | Taux de change vers la devise du bénéficiaire (null si même devise) |
method | string | Méthode de paiement |
Objet fees
"fees": {
"amount": 1230,
"currency": "XAF",
"billed": { "amount": 1230, "currency": "XOF", "rate": 1 }
}
| Champ | Type | Description |
|---|---|---|
amount | number | Frais dans la devise débitée |
currency | string | Devise débitée |
billed.amount | number | Frais tels que calculés par le barème, en XOF |
billed.currency | string | Toujours XOF |
billed.rate | number | Taux XOF → devise débitée |
Objet fx
| Champ | Type | Description |
|---|---|---|
rate | number | Taux de change appliqué |
spreadPercent | number | Marge appliquée (%) |
sourceCurrency | string | Devise source |
destinationCurrency | string | Devise destination |
Solde insuffisant
Quand aucun portefeuille ne couvre le débit et qu’aucun complément n’est possible, le devis échoue en400 avec un corps détaillé — conçu pour être actionnable plutôt que de vous
laisser deviner :
400 - INSUFFICIENT_BALANCE
{
"code": "INSUFFICIENT_BALANCE",
"message": "Solde insuffisant. XOF: 130 disponible pour 632 requis — Convertible depuis: 99180 CDF, relancez le devis avec allowFxFrom.",
"balanceCategory": "collect",
"attempts": [
{ "currency": "XOF", "required": 632, "available": 130 }
],
"convertibleFrom": [
{ "currency": "CDF", "available": 99180 }
]
}
| Champ | Description |
|---|---|
balanceCategory | Catégorie de solde consultée (payout ou collect selon votre mode) |
attempts | Portefeuilles essayés, avec le requis et le disponible |
convertibleFrom | Devises que vous détenez et pouvez autoriser via allowFxFrom |
convertibleFrom vous donne directement la valeur à passer dans allowFxFrom pour relancer
le devis. Une liste vide signifie qu’aucun de vos portefeuilles ne couvre le manque : il faut
alors approvisionner le compte.Le devis expire après 5 minutes. Passé ce délai, vous devez en demander un nouveau.