Intégrer les paiements TekSafi PayIntegrating TekSafi Pay payments
Ce guide s'adresse aux équipes techniques des marchands qui intègrent l'encaissement mobile money via l'API TekSafi Pay. Tous les exemples ci-dessous utilisent des données réelles d'un marché actif (Congo-Brazzaville, XAF, MTN Mobile Money).
This guide is for merchant engineering teams integrating mobile money collection via the TekSafi Pay API. Every example below uses real data from a live market (Congo-Brazzaville, XAF, MTN Mobile Money).
Démarrage rapideQuickstart
Créez votre compte gratuitement (particulier ou marchand) sur teksafipay.com/inscription. Votre application démarre automatiquement en mode Sandbox, sans engagement. (Un compte peut aussi être créé directement par un administrateur TekSafi via le back-office : il démarre alors pré-vérifié, directement en mode Live.)
Create your account for free (individual or merchant) at teksafipay.com/inscription. Your application automatically starts in Sandbox mode, with no commitment. (An account can also be created directly by a TekSafi administrator through the back-office : it then starts pre-verified, directly in Live mode.)
Connectez-vous une première fois pour générer votre clé API et votre clé de collecte (voir Authentification).
Sign in once to generate your API key and your collection key (see Authentication).
Échangez votre clé API contre un jeton de session, puis appelez POST /transactions/collection pour votre premier encaissement simulé (voir Collecter un paiement).
Exchange your API key for a session token, then call POST /transactions/collection for your first simulated collection (see Collecting a payment).
Une fois vos tests concluants, soumettez vos documents KYC depuis le portail marchand. Après validation par un admin, votre compte bascule en mode Live — mêmes appels, mêmes réponses, mais argent réel.
Once your tests pass, submit your KYC documents from the merchant portal. After admin approval, your account switches to Live mode — same calls, same responses, but real money.
curl -X POST https://api.teksafipay.com/api/v1/auth \ -H "x-application-id: 8796c6c4-aa02-403f-bd95-3f7254dda8e9" \ -H "x-api-key: a47f9e21-6c3d-4b8a-9f12-7e4d8c2b91a5"
Sandbox vs LiveSandbox vs Live
Le mode est un attribut de votre compte, pas un paramètre de requête : il n'y a ni en-tête ni flag "test mode" à passer. Tant que votre compte est en Sandbox, chaque appel — collection, décaissement, webhook — passe par un simulateur interne au lieu de l'agrégateur réel ; le contrat de requête/réponse est identique dans les deux modes, seul le traitement change.
Mode is an attribute of your account, not a request parameter : there's no "test mode" header or flag to pass. As long as your account is in Sandbox, every call — collection, disbursement, webhook — runs through an internal simulator instead of the real aggregator; the request/response contract is identical in both modes, only the processing changes.
En Sandbox, un compte LIVE ne voit jamais l'historique Sandbox d'un autre compte, et inversement : les transactions sont toujours filtrées par le mode courant de votre application.
In Sandbox, a LIVE account never sees another account's Sandbox history, and vice versa : transactions are always filtered by your application's current mode.
AuthentificationAuthentication
Toutes les routes de paiement exigent un jeton (JWT) dans l'en-tête Authorization : Bearer <jeton>. Un jeton expire après 1 heure — prévoyez de le renouveler.
Every payment route requires a token (JWT) in the Authorization : Bearer <token> header. A token expires after 1 hour — plan to refresh it.
Obtenir un jetonGetting a token
| MéthodeMethod | UsageUse case |
|---|---|
POST/auth avec en-têteswith headers x-application-id / x-api-key | Échange direct clé → jeton. Recommandé pour un serveur backend.Direct key → token exchange. Recommended for a backend server. |
POST/auth/login-key avecwith { applicationId, key } | Équivalent en JSON plutôt qu'en en-têtes.Same thing, as JSON instead of headers. |
POST /auth/login-key
{
"applicationId": "8796c6c4-aa02-403f-bd95-3f7254dda8e9",
"key": "a47f9e21-6c3d-4b8a-9f12-7e4d8c2b91a5"
}{
"success": true,
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 3600,
"token_type": "Bearer"
}Générer vos clésGenerating your keys
Vos clés démarrent à null à la création du compte — générez-les vous-même une fois connecté (nécessite un jeton obtenu via le dashboard, authMethod email).
Your keys start at null when the account is created — generate them yourself once signed in (requires a token obtained via the dashboard, authMethod email).
| ChampField | Type | RequisRequired | DescriptionDescription |
|---|---|---|---|
| keyType | enum | ouiyes | API_KEY · COLLECTION_KEY · DISBURSEMENT_KEY |
| force | boolean | nonno | Régénère même si une clé existe déjà (l'ancienne devient invalide)Regenerates even if a key already exists (the old one becomes invalid) |
{
"success": true,
"message": "Clé générée avec succès.",
"data": {
"oldKey": null,
"newKey": "a47f9e21-6c3d-4b8a-9f12-7e4d8c2b91a5"
}
}La nouvelle clé est aussi envoyée par email. COLLECTION_KEY et DISBURSEMENT_KEY ne peuvent être générées que si votre compte a respectivement collectionAllowed/disbursementAllowed activé par un admin.
The new key is also emailed to you. COLLECTION_KEY and DISBURSEMENT_KEY can only be generated if your account has collectionAllowed/disbursementAllowed enabled by an admin, respectively.
Trois types de clé, trois usagesThree key types, three purposes
| CléKey | Sert àUsed for |
|---|---|
apiKey | S'authentifier et obtenir un jeton (Authenticating and obtaining a token (Authorization: Bearer) |
collectionKey | Requise en plus du jeton, en en-tête Required alongside the token, as header X-Collection-Key, pourfor POST /transactions/collection |
disbursementKey | Même principe pour les décaissements (Same idea for disbursements (X-Disbursement-Key) |
Découvrir vos marchés & moyens de paiementDiscovering your markets & payment methods
Toujours interroger cette route plutôt que de coder en dur un format de numéro ou un taux : c'est la source de vérité, propre à votre compte, et elle reflète les changements faits par un admin en temps réel.
Always query this route instead of hardcoding a number format or a rate : it's the source of truth for your account, and it reflects changes an admin makes in real time.
{
"success": true,
"data": {
"appName": "Boutique Malonga",
"markets": [
{
"marketId": "f394448f-65e2-46b4-b213-a1159e74ba58",
"marketActive": true,
"country": { "code": "CG", "name": "République du Congo", "callingCode": "+242", "currency": "XAF" },
"paymentMethods": [
{
"code": "MTN_MOMO_COG",
"name": "MTN Mobile Money",
"type": "MOBILE_MONEY",
"regexValidation": "^24206\\d{7}$",
"validationFormat": "242 06XXXXXXX",
"rates": { "collection": 6, "disbursement": 1 }
}
]
}
]
}
}Collecter un paiement (Collection)Collecting a payment (Collection)
Une Collection encaisse de l'argent depuis le mobile money d'un client vers votre wallet TekSafi. C'est une opération asynchrone : la réponse initiale confirme que la demande a été envoyée à l'opérateur, pas qu'elle a réussi.
A Collection pulls money from a customer's mobile money account into your TekSafi wallet. It's an asynchronous operation : the initial response confirms the request was sent to the operator, not that it succeeded.
En-têtes requis en plus de Authorization : X-Collection-Key: <votre collectionKey>.
Required header on top of Authorization : X-Collection-Key: <your collectionKey>.
| ChampField | Type | RequisRequired | DescriptionDescription |
|---|---|---|---|
| countryCode | string | ouiyes | Ex. E.g. CG |
| paymentMethodCode | string | ouiyes | Ex. E.g. MTN_MOMO_COG — voirsee vos moyens disponiblesyour available methods |
| amount | number | ouiyes | Minimum Minimum 0.01, dans l'unité de la devisein the currency's unit |
| currency | string | ouiyes | Doit correspondre à la devise du marchéMust match the market's currency |
| customerInfo.phoneNumber | string | oui pour mobile moneyyes for mobile money | VoirSee formats de téléphonephone number formats |
| reference | string | nonno | Votre identifiant unique — rejouer la même reference renvoie la transaction existante au lieu d'en créer une seconde (idempotence)Your own unique identifier — replaying the same reference returns the existing transaction instead of creating a second one (idempotency) |
| metadata | object | nonno | Libre, restitué tel quelFree-form, echoed back as-is |
POST /transactions/collection
X-Collection-Key: a2f1c9e4-...
{
"countryCode": "CG",
"paymentMethodCode": "MTN_MOMO_COG",
"amount": 5000,
"currency": "XAF",
"customerInfo": { "phoneNumber": "242061234567" },
"reference": "commande-48213"
}{
"id": "985f19c3-11bc-422a-b731-c97ccb0e5e20",
"reference": "commande-48213",
"status": "PENDING",
"amount": "5000.00",
"currency": "XAF",
"providerReference": "367ea115-37ce-4932-a69a-fbfe0e59384f",
"createdAt": "2026-08-14T19:37:41.897Z",
"customerInfo": { "phoneNumber": "242061234567" }
}Ce qui se passe ensuiteWhat happens next
Le client reçoit une invite de confirmation (code PIN) sur son téléphone. Vous devez ensuite vérifier le statut final — voir Statut d'une transaction. Ne considérez jamais un encaissement comme réussi sur la seule foi de la réponse 201 : elle signifie "en cours", pas "payé".
The customer gets a PIN confirmation prompt on their phone. You then need to check the final status — see Transaction status. Never treat a collection as successful just because you got a 201 : it means "in progress", not "paid".
Calcul des fraisFee calculation
À la confirmation, votre wallet est crédité du montant net de deux commissions définies sur le tarif marché (configurées par un admin) : la commission agrégateur et la commission plateforme, chacune un pourcentage du montant brut.
On confirmation, your wallet is credited the amount net of two commissions defined on the market rate (configured by an admin) : the aggregator commission and the platform commission, each a percentage of the gross amount.
| ÉtapeStep | CalculCalculation | ExempleExample (5 000 XAF, 4%+2%) |
|---|---|---|
| Montant brutGross amount | amount | 5 000 |
| Commission agrégateurAggregator commission | amount × aggregatorCollectionRate / 100 | − 200 |
| Commission plateformePlatform commission | amount × platformCollectionRate / 100 | − 100 |
| Crédité sur votre walletCredited to your wallet | montant − les deux commissionsamount − both commissions | 4 700 XAF |
Formats de numéro de téléphonePhone number formats
Chaque moyen de paiement mobile money porte sa propre règle de validation (regexValidation), propre à son pays. TekSafi Pay n'est pas limité au Congo : de nouveaux marchés sont activés au fil du temps, chacun avec son propre format. Ne codez jamais un format en dur dans votre intégration — récupérez-le systématiquement via GET /applications/markets-payment-methods, qui reste la seule source à jour pour les marchés auxquels votre compte est relié.
Every mobile money payment method carries its own validation rule (regexValidation), specific to its country. TekSafi Pay isn't limited to Congo : new markets are activated over time, each with its own format. Never hardcode a format in your integration — always fetch it via GET /applications/markets-payment-methods, which remains the only up-to-date source for the markets your account is linked to.
À titre d'illustration du principe général (indicatif pays + préfixe opérateur local conservé, sans +), voici le marché Congo-Brazzaville :
As an illustration of the general pattern (country code + local operator prefix kept, no +), here is the Congo-Brazzaville market :
| OpérateurOperator | Préfixe localLocal prefix | Format attenduExpected format | ExempleExample |
|---|---|---|---|
| MTN CongoMTN Congo | 06 | 242 + 06 + 7 chiffres (12 au total, pas de digits (12 total, no +) | 242061234567 |
| Airtel CongoAirtel Congo | 04 ouor 05 | 242 + 04/05 + 7 chiffresdigits | 242051234567 |
Le zéro local est conservé — on n'envoie pas un format international "sans zéro" (24261234567). C'est l'erreur la plus fréquente à l'intégration. D'autres marchés (avec leurs propres règles) s'ajoutent à mesure qu'ils sont activés — cette page n'est pas mise à jour à chaque nouveau marché, seul l'endpoint ci-dessus fait foi.
The local zero is kept — you don't send a "no-zero" international format (24261234567). This is the single most common integration mistake. More markets (each with their own rules) are added as they're activated — this page isn't updated for every new market, only the endpoint above is authoritative.
Vérifier le statut d'une transactionChecking a transaction's status
:reference est la reference fournie (ou générée) à la création. Interrogez cette route par intervalles réguliers (quelques secondes) jusqu'à obtenir un statut final.
:reference is the reference you supplied (or that was generated) at creation. Poll this route at regular intervals (a few seconds) until you get a final status.
{
"id": "985f19c3-11bc-422a-b731-c97ccb0e5e20",
"reference": "commande-48213",
"status": "SUCCESS",
"amount": "5000.00",
"currency": "XAF",
"providerReference": "367ea115-37ce-4932-a69a-fbfe0e59384f",
"completedAt": "2026-08-14T19:39:57.453Z",
"type": "collection"
}WebhooksWebhooks
Plutôt que d'interroger GET /transactions/callback/:reference en boucle, enregistrez une URL de webhook : TekSafi vous notifie automatiquement dès qu'une transaction atteint un état définitif.
Instead of polling GET /transactions/callback/:reference in a loop, register a webhook URL : TekSafi notifies you automatically as soon as a transaction reaches a final state.
| ChampField | Type | RequisRequired | DescriptionDescription |
|---|---|---|---|
| webhookUrl | string | ouiyes | URL HTTPS qui recevra les notificationsHTTPS URL that will receive notifications |
| regenerateSecret | boolean | nonno | Force la génération d'un nouveau secret de signature (invalide l'ancien)Forces generation of a new signing secret (invalidates the old one) |
{
"webhookUrl": "https://monapp.cg/webhooks/teksafi"
}{
"success": true,
"message": "Webhook enregistré.",
"data": {
"webhookUrl": "https://monapp.cg/webhooks/teksafi",
"webhookSecret": "676e30a4-58ee-4eeb-a5ca-f0c54e582d10"
}
}Le webhookSecret n'est affiché qu'une seule fois, à la création ou à la régénération. Stockez-le côté serveur — il sert à vérifier que les requêtes viennent bien de TekSafi.
The webhookSecret is shown only once, at creation or regeneration. Store it server-side — it's what lets you verify requests genuinely come from TekSafi.
Ce que vous recevezWhat you receive
Une requête POST vers votre webhookUrl, pour chacun des quatre événements :
A POST request to your webhookUrl, for each of the four events :
| ÉvénementEvent | Déclenché quandFired when |
|---|---|
collection.success | Un encaissement a été confirmé par l'opérateurA collection was confirmed by the operator |
collection.failed | Un encaissement a échoué ou a été refuséA collection failed or was declined |
disbursement.success | Un décaissement a été confirmé par l'agrégateurA disbursement was confirmed by the aggregator |
disbursement.failed | Un décaissement a échouéA disbursement failed |
{
"event": "collection.success",
"data": {
"id": "985f19c3-11bc-422a-b731-c97ccb0e5e20",
"reference": "commande-48213",
"status": "SUCCESS",
"amount": "5000.00",
"currency": "XAF",
"providerReference": "367ea115-37ce-4932-a69a-fbfe0e59384f",
"completedAt": "2026-08-14T19:39:57.453Z",
"type": "collection"
},
"timestamp": "2026-08-14T19:39:57.500Z"
}Vérifier la signatureVerifying the signature
Chaque requête porte un en-tête X-TekSafi-Signature : un HMAC-SHA256 du corps JSON exact (tel quel, avant tout re-formatage), calculé avec votre webhookSecret. Recalculez-le et comparez avant de traiter la notification.
Every request carries an X-TekSafi-Signature header : an HMAC-SHA256 of the exact JSON body (as sent, before any reformatting), computed with your webhookSecret. Recompute it and compare before acting on the notification.
const crypto = require("crypto");
function isValid(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return expected === signatureHeader;
}Réessais & fiabilitéRetries & reliability
3 tentatives par notification (avec un court délai croissant entre chacune) si votre endpoint ne répond pas ou renvoie une erreur. Passé ce délai, la notification est abandonnée : elle n'est pas conservée en file d'attente pour un envoi ultérieur. Votre endpoint doit répondre 2xx rapidement — traitez la notification de façon asynchrone si besoin plutôt que de faire attendre la réponse.
3 attempts per notification (with a short increasing delay between each) if your endpoint doesn't respond or returns an error. After that, the notification is dropped : it is not queued for later delivery. Your endpoint should respond 2xx quickly — process the notification asynchronously if needed rather than making the response wait.
Les webhooks sont un raccourci pratique, pas une garantie de livraison. Gardez toujours GET /transactions/callback/:reference comme filet de sécurité pour les transactions dont vous n'auriez pas reçu de notification.
Webhooks are a convenience, not a delivery guarantee. Always keep GET /transactions/callback/:reference as a safety net for transactions you never got notified about.
Décaisser (Disbursement)Payout (Disbursement)
Un Décaissement envoie de l'argent depuis votre wallet TekSafi vers un client (mobile money) ou un compte bancaire. Comme la Collection, c'est une opération asynchrone : fonctionne à l'identique en mode Sandbox (simulateur) et en mode Live (agrégateur réel) — aucun changement de contrat entre les deux.
A Disbursement sends money out from your TekSafi wallet to a customer (mobile money) or bank account. Like Collection, it's an asynchronous operation : it works identically in Sandbox mode (simulator) and Live mode (real aggregator) — no contract change between the two.
Le décaissement doit être activé explicitement pour votre application (disbursementAllowed, voir Authentification) et nécessite une clé dédiée, distincte de la clé de collecte.
Disbursement must be explicitly enabled for your application (disbursementAllowed, see Authentication) and requires a dedicated key, separate from your collection key.
En-têtes requis en plus de Authorization : X-Disbursement-Key: <votre disbursementKey>.
Required header on top of Authorization : X-Disbursement-Key: <your disbursementKey>.
| ChampField | Type | RequisRequired | DescriptionDescription |
|---|---|---|---|
| countryCode | string | ouiyes | Ex. E.g. CG |
| paymentMethodCode | string | ouiyes | Ex. E.g. ORANGE_MONEY — voirsee vos moyens disponiblesyour available methods |
| amount | number | ouiyes | Minimum Minimum 0.01 — montant effectivement reçu par le destinataire (les frais s'ajoutent, voir plus bas)amount the recipient actually receives (fees are added on top, see below) |
| currency | string | ouiyes | Doit correspondre à la devise du marchéMust match the market's currency |
| recipientInfo.phoneNumber | string | oui pour mobile moneyyes for mobile money | VoirSee formats de téléphonephone number formats |
| reference | string | nonno | Votre identifiant unique — rejouer la même reference renvoie la transaction existante au lieu d'en créer une seconde (idempotence)Your own unique identifier — replaying the same reference returns the existing transaction instead of creating a second one (idempotency) |
| metadata | object | nonno | Libre, restitué tel quelFree-form, echoed back as-is |
POST /transactions/disbursement
X-Disbursement-Key: d9e2b7a1-...
{
"countryCode": "CG",
"paymentMethodCode": "ORANGE_MONEY",
"amount": 3000,
"currency": "XAF",
"recipientInfo": { "phoneNumber": "242061234567" },
"reference": "remboursement-9910"
}{
"id": "12ab34cd-56ef-78a9-bc01-d2e3f4a5b6c7",
"reference": "remboursement-9910",
"status": "PROCESSING",
"amount": "3000.00",
"currency": "XAF",
"providerReference": "9c1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a",
"createdAt": "2026-08-14T19:37:41.897Z",
"recipientInfo": { "phoneNumber": "242061234567" }
}Ce qui se passe ensuiteWhat happens next
Votre wallet est débité du montant + frais avant l'appel à l'agrégateur (solde insuffisant → erreur 400, rien n'est débité). Le statut démarre directement à PROCESSING (pas de PENDING intermédiaire, contrairement à la Collection). Vérifiez le statut final via le même endpoint de statut ou via webhook.
Your wallet is debited the amount + fees before calling the aggregator (insufficient balance → 400 error, nothing gets debited). Status starts directly at PROCESSING (no intermediate PENDING, unlike Collection). Check the final status via the same status endpoint or via webhook.
Calcul des fraisFee calculation
Contrairement à la Collection, les frais s'ajoutent au montant demandé au lieu d'en être déduits : le destinataire reçoit le montant plein, et votre wallet est débité du montant + les deux commissions (agrégateur et plateforme, définies sur le tarif marché).
Unlike Collection, fees are added to the requested amount instead of being deducted from it : the recipient gets the full amount, and your wallet is debited the amount + both commissions (aggregator and platform, defined on the market rate).
| ÉtapeStep | CalculCalculation | ExempleExample (3 000 XAF, 4%+2%) |
|---|---|---|
| Montant reçu par le destinataireAmount received by the recipient | amount | 3 000 |
| Commission agrégateurAggregator commission | amount × aggregatorDisbursementRate / 100 | + 120 |
| Commission plateformePlatform commission | amount × platformDisbursementRate / 100 | + 60 |
| Débité de votre walletDebited from your wallet | montant + les deux commissionsamount + both commissions | 3 180 XAF |
Historique & statistiquesHistory & statistics
| ChampField | DescriptionDescription |
|---|---|
| type | collection ouor disbursement |
| status | PENDING·PROCESSING·SUCCESS·FAILED·REVERSED |
| startDate / endDate | ISO 8601 |
| limit | défaut 50default 50 |
{
"success": true,
"data": [ { "id": "...", "status": "SUCCESS", "amount": "5000.00", "..." : "..." } ],
"meta": { "total": 42, "totalPages": 1, "page": 1, "limite": 50 }
}timeRange requis : 7d · 30d · 3m.
timeRange required : 7d · 30d · 3m.
{
"success": true,
"data": {
"stat": [ { "date": "2026-08-01", "collection": 3, "disbursement": 0 } ],
"volume": { "collection": 42, "disbursement": 0 },
"growth": { "collection": 12.5, "disbursement": 0 }
}
}Solde & walletBalance & wallet
Renvoie chacun de vos wallets (un par marché/devise) avec les moyens de paiement actifs et les taux qui s'y appliquent.
Returns each of your wallets (one per market/currency) with the active payment methods and rates that apply to it.
[
{
"id": "431a6abc-1cf5-44bf-b2b4-32bf85f7283a",
"balance": "4700.00",
"country": { "code": "CG" },
"currency": { "code": "XAF" },
"paymentMethods": [ { "code": "MTN_MOMO_COG", "rates": { "totalCollection": 6, "totalDisbursement": 1 } } ]
}
]Demander un retraitRequesting a withdrawal
Estimer les frais — POST /wallets/:walletId/withdrawal/fees avec { amount }. Renvoie fee, netAmount, et si votre solde suffit.
Estimate the fee — POST /wallets/:walletId/withdrawal/fees with { amount }. Returns fee, netAmount, and whether your balance is sufficient.
Créer la demande — POST /wallets/withdrawal-requests. Le montant est débité de votre wallet immédiatement ; la demande passe ensuite en traitement côté TekSafi.
Create the request — POST /wallets/withdrawal-requests. The amount is debited from your wallet immediately; the request then goes into processing on TekSafi's side.
Suivre l'avancement — GET /wallets/withdrawal-requests/:id/status, ou annuler tant qu'elle est encore PENDING via PATCH /wallets/withdrawal-requests/:id/cancel (remboursement automatique du wallet).
Track progress — GET /wallets/withdrawal-requests/:id/status, or cancel while it's still PENDING via PATCH /wallets/withdrawal-requests/:id/cancel (automatic wallet refund).
| ChampField | Type | RequisRequired | DescriptionDescription |
|---|---|---|---|
| walletId | string | ouiyes | Doit vous appartenir et être actifMust belong to you and be active |
| amount | number | ouiyes | Minimum absolu Absolute minimum 100 |
| paymentDetails | object | ouiyes | Exactement l'un des deux : Exactly one of the two : mobileMoney ouor bankAccount |
| priority | enum | non — déf. NORMALno — def. NORMAL | LOW·NORMAL·HIGH·URGENT — influence le délai estiméaffects the estimated delay |
{
"walletId": "431a6abc-1cf5-44bf-b2b4-32bf85f7283a",
"amount": 4000,
"paymentDetails": {
"mobileMoney": { "provider": "MTN_MOMO_COG", "phoneNumber": "242061234567" }
}
}{
"success": true,
"message": "Demande de retrait créée, wallet débité",
"data": { "id": "...", "status": "PENDING", "amount": "4000.00" }
}Référence des erreursError reference
| StatutStatus | CauseCause | ExempleExample |
|---|---|---|
400 | Corps de requête invalide ou règle métier non respectéeInvalid request body or a business rule wasn't met | { "statusCode": 400, "message": "amount must not be less than 0.01", "error": "Bad Request" } |
401 | Jeton absent, expiré, ou de mauvais type (ex. jeton "email" utilisé sur une route "apikey")Missing or expired token, or the wrong type (e.g. an "email" token used on an "apikey" route) | { "statusCode": 401, "message": "This route requires apikey authentication" } |
403 | Compte authentifié mais rôle insuffisant (route réservée ADMIN)Authenticated, but insufficient role (route reserved for ADMIN) | { "statusCode": 403, "message": "Cette action nécessite le rôle ADMIN." } |
404 | Ressource introuvable (marché, moyen de paiement, transaction…)Resource not found (market, payment method, transaction…) | — |
429 | Trop de demandes de code OTP (max 5/heure)Too many OTP code requests (max 5/hour) | "Trop de tentatives. Veuillez attendre 1 heure." |
Les erreurs de validation de champ renvoient un tableau de messages, un par règle non respectée :
Field validation errors return an array of messages, one per failed rule :
{
"statusCode": 400,
"message": [
"amount must not be less than 0.01",
"currency must be a string"
],
"error": "Bad Request"
}Format des réponsesResponse format
La majorité des routes de lecture/écriture (comptes, transactions, wallet) renvoient une enveloppe standard :
Most read/write routes (accounts, transactions, wallet) return a standard envelope :
{
"success": true,
"message": "...",
"data": { }
}Sur certaines routes de configuration, la réponse est directement l'objet créé/modifié, sans enveloppe — c'est indiqué à chaque endpoint de cette documentation par l'exemple de réponse fourni. En cas de doute, fiez-vous toujours à l'exemple, pas à une règle générale.
On some configuration routes, the response is directly the created/updated object, with no envelope — this is shown at every endpoint in this documentation via the response example provided. When in doubt, always trust the example, not a general rule.