{"openapi":"3.1.0","info":{"title":"B-Cash API","description":"API marchande B-Cash \u2014 wallet \u00e0 solde r\u00e9el, frais dynamiques, ex\u00e9cution mobile money.","version":"1.4.0"},"servers":[{"url":"https:\/\/api.gba-cm.online","description":"Production ET sandbox (environnement port\u00e9 par le credential : pk_test_\/sk_test_ = sandbox avec simulateur, pk_live_\/sk_live_ = production). TLS 1.2 minimum. D\u00e9tails : GET \/api\/v1\/meta -> environments."}],"paths":{"\/api\/v1\/agent\/enroll":{"post":{"operationId":"agentEnroll","tags":["Agent hub"],"responses":{"201":{"description":"Terminal appair\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"deviceReference":"dev_01J8ZQ4M","hubReference":"hub_MAIN","sims":[{"simProfileReference":"sim_01J8ZQ7X","operator":"MTN","slot":0}],"requiredPackages":["MTN_CM_FR@1.1.0"]}}}},"401":{"description":"ENROLLMENT_TOKEN_INVALID \u2014 jeton invalide ou expir\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"409":{"description":"DEVICE_ALREADY_ENROLLED \u2014 terminal d\u00e9j\u00e0 appair\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Appairer un terminal du HUB","description":"\u00c9change un jeton d'enr\u00f4lement \u00e0 usage unique (TTL 15 min, g\u00e9n\u00e9r\u00e9 au tableau de bord) contre une identit\u00e9 de terminal. Seule requ\u00eate NON sign\u00e9e du cycle de vie : le terminal n'a pas encore de r\u00e9f\u00e9rence. Il g\u00e9n\u00e8re sa paire de cl\u00e9s dans le Keystore mat\u00e9riel et ne transmet que la cl\u00e9 PUBLIQUE (DER X.509 base64) : aucun secret partag\u00e9 ne circule.\n\n**Seule route agent disponible m\u00eame avec `BCASH_HUB_ENABLED=0`** : la s\u00e9quence de mise en service appaire les terminaux AVANT d'activer le hub. Appairer ne d\u00e9clenche aucune ex\u00e9cution \u2014 tant que le hub est d\u00e9sactiv\u00e9, le terminal n'obtient aucun travail.","requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"enrollmentToken":{"type":"string","description":"Jeton \u00e0 usage unique, TTL 15 min"},"devicePublicKey":{"type":"string","description":"Cl\u00e9 publique DER X.509 base64 (Keystore Android)"},"androidId":{"type":"string"},"deviceModel":{"type":"string"}},"required":["enrollmentToken","devicePublicKey","androidId","deviceModel"]}}},"required":true},"security":[]}},"\/api\/v1\/agent\/heartbeat":{"post":{"operationId":"agentHeartbeat","tags":["Agent hub"],"responses":{"200":{"description":"Directives serveur","content":{"application\/json":{"schema":{"type":"object"},"example":{"serverTime":"2026-07-25T12:00:00+00:00","clockSkewSeconds":0,"directives":{"pollAfterSeconds":5,"requiredPackages":["MTN_CM_FR@1.1.0"],"quarantined":false},"simDirectives":[{"simProfileReference":"sim_01J8ZQ7X","eligibilityState":"ACTIVE"}]}}}},"401":{"description":"DEVICE_SIGNATURE_MISSING | MALFORMED | CLOCK_SKEW | INVALID | REPLAY | UNKNOWN","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"403":{"description":"DEVICE_REVOKED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"503":{"description":"HUB_DISABLED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Signal de vie du terminal","description":"\u00c9tat applicatif et \u00e9tat des SIM. `pinConfigured` est un BOOL\u00c9EN : le code PIN ne quitte jamais le terminal et n'appara\u00eet dans aucune requ\u00eate. Un terminal signalant `accessibilityServiceEnabled=false` est inapte au multi-\u00e9tapes : ses SIM passent OFFLINE. `clockSkewSeconds` est calcul\u00e9 et renvoy\u00e9 \u2014 l'horloge conditionne l'acceptation des requ\u00eates sign\u00e9es (fen\u00eatre 300 s). Silencieux > 3 intervalles = hors ligne.\n\nRenvoie **503 HUB_DISABLED** tant que `BCASH_HUB_ENABLED=0`.","requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"deviceReference":{"type":"string"},"appVersion":{"type":"string"},"androidApiLevel":{"type":"integer"},"capturedAt":{"type":"string","format":"date-time"},"accessibilityServiceEnabled":{"type":"boolean"},"packages":{"type":"array","items":{"type":"string"},"description":"Identifiants PKG@version pr\u00e9sents"},"sims":{"type":"array","items":{"type":"object","properties":{"simProfileReference":{"type":"string"},"slot":{"type":"integer"},"present":{"type":"boolean"},"signalLevel":{"type":"integer","nullable":true},"pinConfigured":{"type":"boolean","description":"BOOL\u00c9EN seulement \u2014 jamais le PIN"}}}}},"required":["deviceReference","appVersion","capturedAt","accessibilityServiceEnabled","sims"]}}},"required":true},"security":[{"HubDeviceSignature":[]}]}},"\/api\/v1\/agent\/jobs\/next":{"get":{"operationId":"agentNextJob","tags":["Agent hub"],"responses":{"200":{"description":"Enveloppe de job","content":{"application\/json":{"schema":{"type":"object"},"example":{"job":{"jobId":"job_01J8ZR2K","operationType":"BALANCE_CHECK","transactionReference":null,"operator":"MTN","simProfileReference":"sim_01J8ZQ7X","packageId":"MTN_CM_FR@1.1.0","amount":null,"counterpartyPhone":null,"expectedCounterpartyName":null,"attempt":1,"expiresAt":"2026-07-25T12:04:00+00:00"},"pollAfterSeconds":5}}}},"401":{"description":"Signature de terminal refus\u00e9e","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"409":{"description":"DEVICE_BUSY \u2014 un travail est d\u00e9j\u00e0 en cours (retryable)","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"426":{"description":"PACKAGE_VERSION_MISMATCH","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"503":{"description":"HUB_DISABLED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"R\u00e9clamer le prochain job (polling court)","description":"R\u00e9ponse imm\u00e9diate, `job: null` s'il n'y a rien ; la cadence est impos\u00e9e par `pollAfterSeconds`. Pas de long-poll : un worker PHP-FPM immobilis\u00e9 par terminal n'est pas supportable en h\u00e9bergement mutualis\u00e9.\n\nUn terminal ne d\u00e9tient qu'UN travail \u00e0 la fois (verrou serveur + bail ~240 s) : si un job non conclu et non expir\u00e9 existe d\u00e9j\u00e0, **409 DEVICE_BUSY** (retryable, retryAfter ~15). Le job porte la version EXACTE du package attendu et ne contient JAMAIS de secret \u2014 le PIN est r\u00e9solu localement.\n\nRenvoie **503 HUB_DISABLED** tant que `BCASH_HUB_ENABLED=0`.","security":[{"HubDeviceSignature":[]}]}},"\/api\/v1\/agent\/observations":{"post":{"operationId":"agentObservations","tags":["Agent hub"],"responses":{"202":{"description":"Observations accept\u00e9es","content":{"application\/json":{"schema":{"type":"object"},"example":{"accepted":3}}}},"401":{"description":"Signature de terminal refus\u00e9e","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"503":{"description":"HUB_DISABLED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Verser des \u00e9crans\/SMS non reconnus au corpus","description":"Hors session. AUCUNE d\u00e9cision financi\u00e8re n'est prise \u00e0 partir de ces observations : elles constituent le corpus des parcours financiers non encore captur\u00e9s, sans figer pr\u00e9matur\u00e9ment des r\u00e8gles de parsing.\n\nRenvoie **503 HUB_DISABLED** tant que `BCASH_HUB_ENABLED=0`.","requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"observations":{"type":"array","maxItems":50,"items":{"type":"object","properties":{"observedAt":{"type":"string","format":"date-time"},"channel":{"type":"string","enum":["USSD","SMS","SCREEN"]},"rawText":{"type":"string"},"matchStatus":{"type":"string"},"ruleId":{"type":"string","nullable":true},"smsSender":{"type":"string","nullable":true}}}}},"required":["observations"]}}},"required":true},"security":[{"HubDeviceSignature":[]}]}},"\/api\/v1\/agent\/operator-packages\/{packageId}":{"get":{"operationId":"agentOperatorPackage","tags":["Agent hub"],"responses":{"200":{"description":"Package sign\u00e9 (voir en-t\u00eate X-BCash-Package-Signature)","content":{"application\/json":{"schema":{"type":"object"},"example":{"packageId":"MTN_CM_FR@1.1.0","operator":"MTN","content":{"ussdFlows":["\u2026"],"screenRules":["\u2026"],"smsRules":["\u2026"]}}}},"headers":{"X-BCash-Package-Signature":{"schema":{"type":"string"},"description":"base64(ECDSA_P256_SHA256(cl\u00e9_\u00e9diteur, contenu canonique)) \u2014 v\u00e9rifi\u00e9e par le terminal."}}},"404":{"description":"PACKAGE_NOT_FOUND \u2014 version inconnue","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"503":{"description":"HUB_DISABLED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"T\u00e9l\u00e9charger un package op\u00e9rateur (version exacte, sign\u00e9)","description":"Distribue le package (cha\u00eenes USSD d\u00e9compos\u00e9es, r\u00e8gles d'\u00e9cran et de SMS) dans la version exacte demand\u00e9e \u2014 corriger une r\u00e8gle de parsing SANS republier l'APK.\n\nL'en-t\u00eate de r\u00e9ponse **X-BCash-Package-Signature** est OBLIGATOIRE : le terminal refuse tout package dont la signature ne se v\u00e9rifie pas contre la cl\u00e9 publique embarqu\u00e9e \u00e0 la compilation \u2014 une r\u00e8gle de lecture d'\u00e9cran conditionne des d\u00e9cisions financi\u00e8res, elle ne peut pas venir d'une source non authentifi\u00e9e.\n\nRenvoie **503 HUB_DISABLED** tant que `BCASH_HUB_ENABLED=0`.","parameters":[{"name":"packageId","in":"path","description":"Identifiant complet versionn\u00e9, ex. MTN_CM_FR@1.1.0","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HubDeviceSignature":[]}]}},"\/api\/v1\/agent\/sessions\/{jobId}\/result":{"post":{"operationId":"agentSessionResult","tags":["Agent hub"],"responses":{"200":{"description":"Compte rendu accept\u00e9 (ou doublon inoffensif)","content":{"application\/json":{"schema":{"type":"object"},"example":{"jobId":"job_01J8ZR2K","accepted":true,"transactionStatus":null,"duplicate":false}}}},"404":{"description":"JOB_NOT_FOUND \u2014 inconnu ou non attribu\u00e9 \u00e0 ce terminal","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"409":{"description":"SESSION_ALREADY_CONCLUDED \u2014 compte rendu divergent","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"422":{"description":"SESSION_RESULT_INVALID \u2014 incoh\u00e9rent avec le type d'op\u00e9ration","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"503":{"description":"HUB_DISABLED","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Conclure une session d'ex\u00e9cution (idempotent, immuable)","description":"La route la plus d\u00e9licate du contrat : le compte rendu remonte juste apr\u00e8s l'ex\u00e9cution, au moment o\u00f9 une coupure r\u00e9seau est la plus probable et la plus co\u00fbteuse.\n\n`outcome` \u2208 SUCCESS | FAILED_NO_FINANCIAL_EFFECT | RETRYABLE_NO_FINANCIAL_EFFECT | INDETERMINATE. `balances` n'est pr\u00e9sent QUE pour BALANCE_CHECK.\n\n**INDETERMINATE ne devient jamais un \u00e9chec** : transaction en PENDING_RECONCILIATION, r\u00e9servation de float RETENUE.\n\nIdempotence : un jobId d\u00e9j\u00e0 conclu avec un compte rendu IDENTIQUE renvoie 200 avec `duplicate: true` et le r\u00e9sultat d'origine \u2014 le rejeu apr\u00e8s coupure est inoffensif. Un compte rendu DIVERGENT est refus\u00e9 (**409 SESSION_ALREADY_CONCLUDED**) : un r\u00e9sultat ne se r\u00e9\u00e9crit pas.\n\nRenvoie **503 HUB_DISABLED** tant que `BCASH_HUB_ENABLED=0`.","parameters":[{"name":"jobId","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^job_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false},{"name":"Idempotency-Key","in":"header","description":"D\u00e9riv\u00e9e du job, STABLE sur toutes les tentatives de remont\u00e9e.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"},"outcome":{"type":"string","enum":["SUCCESS","FAILED_NO_FINANCIAL_EFFECT","RETRYABLE_NO_FINANCIAL_EFFECT","INDETERMINATE"]},"failureCode":{"type":"string","nullable":true},"startedAt":{"type":"string","format":"date-time"},"endedAt":{"type":"string","format":"date-time"},"packageId":{"type":"string"},"operatorReference":{"type":"string","nullable":true},"balances":{"type":"object","nullable":true,"description":"BALANCE_CHECK uniquement","properties":{"mobileMoneyBalance":{"type":"integer"},"rechargeBalance":{"type":"integer"},"commissionBalance":{"type":"integer"}}},"counterpartyName":{"type":"string","nullable":true},"observations":{"type":"array","maxItems":50,"items":{"type":"object","properties":{"observedAt":{"type":"string","format":"date-time"},"channel":{"type":"string"},"rawText":{"type":"string"},"matchStatus":{"type":"string"},"ruleId":{"type":"string","nullable":true}}}}},"required":["jobId","outcome","startedAt","endedAt","packageId"]}}},"required":true},"security":[{"HubDeviceSignature":[]}]}},"\/api\/auth\/me":{"get":{"operationId":"authMe","tags":["Auth (dashboard)"],"responses":{"200":{"description":"Identit\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"email":"admin@bcash.test","name":"Super Admin","roles":["ROLE_SUPER_ADMIN","ROLE_DASHBOARD"],"merchant":null}}}},"401":{"description":"JWT absent ou expir\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Identit\u00e9 de l'utilisateur connect\u00e9","description":"Email, nom, r\u00f4les et marchand rattach\u00e9 (null pour un administrateur plateforme).","security":[{"JWT":[]}]}},"\/api\/dashboard\/applications":{"get":{"operationId":"dashApplicationList","tags":["Dashboard \u2014 Applications"],"responses":{"200":{"description":"Applications \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Lister les applications marchandes","description":"Le secret n'est jamais renvoy\u00e9 : seuls la cl\u00e9 publique et `secretLast4` sont expos\u00e9s.","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/applications\/{reference}\/credentials":{"post":{"operationId":"dashApplicationRotate","tags":["Dashboard \u2014 Applications"],"responses":{"201":{"description":"Nouveau credential (secret affich\u00e9 une seule fois)","content":{"application\/json":{"schema":{"type":"object"},"example":{"publicKey":"pk_test_ab12\u2026","secret":"sk_test_9f3c\u2026","secretLast4":"f3c1","environment":"sandbox","scopes":["transactions:create"]}}}},"403":{"description":"R\u00f4le insuffisant ou application hors p\u00e9rim\u00e8tre","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"G\u00e9n\u00e9rer \/ faire tourner les credentials (TOKEN-ONCE)","description":"Le secret en clair n'est retourn\u00e9 qu'ici, **une seule fois** ; il n'est jamais re-consultable (seul `secretLast4` reste affichable). En cas de perte, il faut refaire tourner la cl\u00e9.\n\n**Deux modes (API-P1-003)** :\n- `mode: \"immediate\"` (d\u00e9faut) \u2014 les credentials actifs sont R\u00c9VOQU\u00c9S aussit\u00f4t : toute int\u00e9gration utilisant l'ancienne cl\u00e9 recevra imm\u00e9diatement `INVALID_SIGNATURE`. Op\u00e9ration destructive.\n- `mode: \"overlap\"` \u2014 les anciens credentials RESTENT actifs : d\u00e9ployez la nouvelle cl\u00e9 sans interruption, puis r\u00e9voquez l'ancienne explicitement via ...\/credentials\/{publicKey}\/revoke.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"app_01HXYZ\u2026"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"description":"Optionnel.","content":{"application\/json":{"schema":{"type":"object","properties":{"environment":{"type":"string","enum":["sandbox","production"],"default":"sandbox"},"scopes":{"type":"array","items":{"type":"string"}},"mode":{"type":"string","enum":["immediate","overlap"],"default":"immediate","description":"overlap = bascule sans interruption (API-P1-003)."}}}}},"required":false},"security":[{"JWT":[]}]}},"\/api\/dashboard\/applications\/{reference}\/credentials\/{publicKey}\/revoke":{"post":{"operationId":"dashCredentialRevoke","tags":["Dashboard \u2014 Applications"],"responses":{"200":{"description":"Credential r\u00e9voqu\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"publicKey":"pk_test_ab12\u2026","status":"REVOKED"}}}},"403":{"description":"R\u00f4le insuffisant ou application hors p\u00e9rim\u00e8tre","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"404":{"description":"Credential introuvable","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"R\u00e9voquer explicitement un credential (cl\u00f4ture d'un chevauchement)","description":"API-P1-003 \u2014 termine une rotation en mode `overlap`. La r\u00e9vocation invalide aussi, en cascade, tous les jetons \u00e9ph\u00e9m\u00e8res `bt_\u2026` \u00e9mis par ce credential, m\u00eame non expir\u00e9s.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false},{"name":"publicKey","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"pk_test_ab12\u2026"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/audit-logs":{"get":{"operationId":"dashAuditList","tags":["Dashboard \u2014 Audit"],"responses":{"200":{"description":"Entr\u00e9es d'audit \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Journal d'audit","description":"Chaque action sensible y laisse une trace, avec son **motif** dans `metadata.reason`.","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"target","in":"query","description":"R\u00e9f\u00e9rence de l'objet cibl\u00e9.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"action","in":"query","description":"ex. `wallet.frozen`, `credential.rotated`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/fee-rules":{"get":{"operationId":"dashFeeRuleList","tags":["Dashboard \u2014 Frais"],"responses":{"200":{"description":"R\u00e8gles de frais \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}},"403":{"description":"R\u00f4le insuffisant","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Lister les r\u00e8gles de frais versionn\u00e9es","description":"Lecture seule pour COMPLIANCE. Le dashboard ne calcule **jamais** de frais : le montant provient toujours de `POST \/api\/dashboard\/operations\/quote`.","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"status","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/fee-rules\/{reference}\/status":{"post":{"operationId":"dashFeeRuleStatus","tags":["Dashboard \u2014 Frais"],"responses":{"200":{"description":"Statut mis \u00e0 jour"},"403":{"description":"R\u00f4le insuffisant","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Activer \/ suspendre une r\u00e8gle (motif obligatoire)","description":"**R\u00e9serv\u00e9 \u00e0 SUPER_ADMIN.** Attention : suspendre une r\u00e8gle fait basculer les transactions correspondantes sur la r\u00e8gle suivante par ordre de priorit\u00e9 \u2014 ou les fait \u00e9chouer en `FEE_RULE_NOT_FOUND` si aucune ne correspond.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACTIVE","SUSPENDED","ARCHIVED"]},"reason":{"type":"string","minLength":3}},"required":["status","reason"]}}},"required":true},"security":[{"JWT":[]}]}},"\/api\/dashboard\/hubs":{"get":{"operationId":"dashHubList","tags":["Dashboard \u2014 Hubs"],"responses":{"200":{"description":"Hubs \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Lister les hubs d'ex\u00e9cution (LECTURE SEULE)","description":"Place r\u00e9serv\u00e9e : le hub Android est un d\u00e9veloppement ult\u00e9rieur.","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/operations":{"post":{"operationId":"dashOperationCreate","tags":["Dashboard \u2014 Op\u00e9rations"],"responses":{"201":{"description":"Transaction cr\u00e9\u00e9e","content":{"application\/json":{"schema":{"type":"object"},"example":{"reference":"txn_01H\u2026","status":"QUEUED","amount":15000,"feeAmount":225,"netAmount":14775,"currency":"XAF"}}}},"409":{"description":"Cl\u00e9 d'idempotence r\u00e9utilis\u00e9e avec un corps diff\u00e9rent","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"422":{"description":"Solde insuffisant \/ r\u00e8gle de frais absente","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Initier une op\u00e9ration (idempotent)","description":"R\u00e9serv\u00e9 aux administrateurs et aux r\u00f4les marchand Owner\/Finance.\n\nLa transaction est cr\u00e9\u00e9e puis mise en file (`QUEUED`) : c'est le **worker Messenger** qui l'ex\u00e9cute et poste les \u00e9critures comptables. Si aucun worker ne tourne, elle reste en `QUEUED`.","parameters":[{"name":"Idempotency-Key","in":"header","description":"UUID. OBLIGATOIRE. Rejouer la m\u00eame cl\u00e9 avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; avec un corps diff\u00e9rent renvoie 409 IDEMPOTENCY_CONFLICT.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","example":"WALLET_TOPUP"},"context":{"type":"string","default":"DASHBOARD"},"amount":{"type":"integer","example":15000},"currency":{"type":"string","default":"XAF"},"operator":{"type":"string","nullable":true},"customer":{"type":"object","nullable":true,"properties":{"phone":{"type":"string","example":"+237650000000"},"displayName":{"type":"string"}}},"merchantReference":{"type":"string","nullable":true},"sourceWalletId":{"type":"string","nullable":true},"destinationWalletId":{"type":"string","nullable":true}},"required":["type","amount"]}}},"required":true},"security":[{"JWT":[]}]}},"\/api\/dashboard\/operations\/quote":{"post":{"operationId":"dashOperationQuote","tags":["Dashboard \u2014 Op\u00e9rations"],"responses":{"200":{"description":"Quote","content":{"application\/json":{"schema":{"type":"object"},"example":{"reference":"feeq_01H\u2026","grossAmount":15000,"feeAmount":225,"netAmount":14775,"currency":"XAF","payer":"CUSTOMER","ruleVersion":"fee_01H\u2026:v1"}}}},"422":{"description":"Aucune r\u00e8gle applicable (FEE_RULE_NOT_FOUND)","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Calculer les frais (aucun effet)","description":"Applique le moteur de r\u00e8gles versionn\u00e9es et fige un snapshot. Aucun mouvement d'argent.","requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","example":"WALLET_TOPUP"},"context":{"type":"string","example":"DASHBOARD"},"amount":{"type":"integer","description":"XAF \u2014 ENTIER, z\u00e9ro d\u00e9cimale.","example":15000},"currency":{"type":"string","default":"XAF"},"operator":{"type":"string","nullable":true,"example":"MTN"}},"required":["type","amount"]}}},"required":true},"security":[{"JWT":[]}]}},"\/api\/dashboard\/transactions":{"get":{"operationId":"dashTransactionList","tags":["Dashboard \u2014 Transactions"],"responses":{"200":{"description":"Transactions \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Lister les transactions","description":"Un utilisateur marchand est automatiquement **scop\u00e9 \u00e0 son marchand** (le scoping est appliqu\u00e9 c\u00f4t\u00e9 serveur, il n'est pas contournable par la requ\u00eate).","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"status","in":"query","description":"SUCCEEDED, FAILED, PENDING_CONFIRMATION\u2026","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"type","in":"query","description":"WALLET_TOPUP, CASH_OUT, MERCHANT_PAYMENT\u2026","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"context","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"operator","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"reference","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"merchantReference","in":"query","description":"Recherche partielle.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/transactions\/{reference}":{"get":{"operationId":"dashTransactionShow","tags":["Dashboard \u2014 Transactions"],"responses":{"200":{"description":"D\u00e9tail complet","content":{"application\/json":{"schema":{"type":"object"},"example":{"reference":"txn_01HXYZ","status":"SUCCEEDED","amount":15000,"feeAmount":225,"events":[{"at":"2026-07-14T09:00:00+00:00","label":"transaction.succeeded"}],"ledger":[{"account":"OPERATOR_FLOAT:MTN","direction":"DEBIT","amount":15000},{"account":"acct_01H\u2026","direction":"CREDIT","amount":14775},{"account":"PLATFORM_FEES","direction":"CREDIT","amount":225}],"execution":[],"webhooks":[]}}}},"403":{"description":"Hors de votre p\u00e9rim\u00e8tre marchand","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"404":{"description":"Transaction introuvable","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"D\u00e9tail d'une transaction","description":"Retourne le r\u00e9sum\u00e9 plus quatre blocs : `events` (chronologie issue de l'audit), `ledger` (\u00e9critures en partie double, \u03a3 d\u00e9bits = \u03a3 cr\u00e9dits), `execution` (sessions mobile money ; la r\u00e9ponse brute de l'op\u00e9rateur est **masqu\u00e9e** pour les r\u00f4les Support\/Viewer) et `webhooks`.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"txn_01HXYZ\u2026"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/wallets":{"get":{"operationId":"dashWalletList","tags":["Dashboard \u2014 Wallets"],"responses":{"200":{"description":"Comptes \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Lister les comptes","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"ownerType","in":"query","description":"CUSTOMER, MERCHANT ou SYSTEM.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"status","in":"query","description":"ACTIVE ou FROZEN.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"reference","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/wallets\/{reference}":{"get":{"operationId":"dashWalletShow","tags":["Dashboard \u2014 Wallets"],"responses":{"200":{"description":"Compte + ledger"},"404":{"description":"Compte introuvable","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"D\u00e9tail d'un compte + 25 derni\u00e8res \u00e9critures","description":"Le solde disponible et le solde bloqu\u00e9 (holds) sont distincts : une r\u00e9servation d\u00e9place des fonds de `availableBalance` vers `lockedBalance` sans \u00e9criture d\u00e9finitive.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"acct_01HXYZ\u2026"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/wallets\/{reference}\/freeze":{"post":{"operationId":"dashWalletFreeze","tags":["Dashboard \u2014 Wallets"],"responses":{"200":{"description":"Compte gel\u00e9"},"400":{"description":"Motif manquant (VALIDATION_FAILED)","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}},"403":{"description":"R\u00f4le insuffisant","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Geler un compte (motif obligatoire)","description":"**R\u00e9serv\u00e9 \u00e0 SUPER_ADMIN et COMPLIANCE.** Un compte gel\u00e9 refuse toute r\u00e9servation de fonds. Le motif est enregistr\u00e9 dans le journal d'audit.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"description":"Motif du gel (min. 3 caract\u00e8res).","content":{"application\/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"example":"Suspicion de fraude \u2014 ticket #4312"}},"required":["reason"]}}},"required":true},"security":[{"JWT":[]}]}},"\/api\/dashboard\/wallets\/{reference}\/unfreeze":{"post":{"operationId":"dashWalletUnfreeze","tags":["Dashboard \u2014 Wallets"],"responses":{"200":{"description":"Compte r\u00e9activ\u00e9"},"403":{"description":"R\u00f4le insuffisant","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"D\u00e9geler un compte (motif obligatoire)","description":"**R\u00e9serv\u00e9 \u00e0 SUPER_ADMIN et COMPLIANCE.**","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"description":"Motif du d\u00e9gel.","content":{"application\/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"example":"Suspicion de fraude \u2014 ticket #4312"}},"required":["reason"]}}},"required":true},"security":[{"JWT":[]}]}},"\/api\/dashboard\/webhook-deliveries":{"get":{"operationId":"dashWebhookList","tags":["Dashboard \u2014 Webhooks"],"responses":{"200":{"description":"Livraisons \u2014 enveloppe { items: [...], total: n }","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[],"total":0}}}}},"summary":"Lister les livraisons de webhooks","parameters":[{"name":"page","in":"query","description":"Page (d\u00e9faut 1).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"Taille de page (max 100, d\u00e9faut 25).","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25},"style":"form","explode":false,"allowReserved":false},{"name":"order","in":"query","description":"Tri, ex. `createdAt:desc`.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"status","in":"query","description":"PENDING, DELIVERED, FAILED, DEAD.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"eventType","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/dashboard\/webhook-deliveries\/{eventId}\/replay":{"post":{"operationId":"dashWebhookReplay","tags":["Dashboard \u2014 Webhooks"],"responses":{"200":{"description":"Replay programm\u00e9"},"404":{"description":"Livraison introuvable","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Rejouer une livraison","description":"R\u00e9-enfile la livraison aupr\u00e8s du worker Messenger. Le marchand doit traiter ses webhooks de fa\u00e7on idempotente (`X-BCash-Event-Id`) : un replay peut produire un doublon de r\u00e9ception.","parameters":[{"name":"eventId","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"evt_01HXYZ\u2026"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"JWT":[]}]}},"\/api\/v1\/fees\/quote":{"post":{"operationId":"api_v1feesquote_post","tags":["FeeQuote"],"responses":{"200":{"description":"Devis d\u00e9compos\u00e9","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/FeeQuoteResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"R\u00e8gle m\u00e9tier non satisfaite","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Devis de frais (aucun effet) \u2014 bases GROSS \/ ORDER_NET","description":"API-P0-003\/004 \u2014 `amount` OBLIGATOIRE ; `amountBasis` l\u00e8ve toute ambigu\u00eft\u00e9 :\nGROSS : amount = d\u00e9bit\u00e9 au client, le marchand absorbe (merchantNet = amount - frais).\nORDER_NET : amount = net commande, frais EN SUS (gross-up EXACT, customerPayable = amount + frais).\n\nValide 900 s (expiresAt). R\u00e9utilisation : cr\u00e9er avec feeQuoteId ET amount = customerPayableAmount.","parameters":[],"requestBody":{"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/FeeQuoteCreateRequest"}}},"required":true},"deprecated":false,"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/auth\/login":{"post":{"operationId":"login_check_post","tags":["Login Check"],"responses":{"200":{"description":"User token created","content":{"application\/json":{"schema":{"type":"object","properties":{"token":{"readOnly":true,"type":"string","nullable":false}},"required":["token"]}}}}},"summary":"Creates a user token.","description":"Creates a user token.","requestBody":{"description":"The login data","content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","nullable":false},"password":{"type":"string","nullable":false}},"required":["email","password"]}}},"required":true}}},"\/api\/v1\/auth\/token":{"post":{"operationId":"merchantMintToken","tags":["Marchand (HMAC)"],"responses":{"201":{"description":"Jeton \u00e9mis","content":{"application\/json":{"schema":{"type":"object"},"example":{"token":"bt_eyJjaWQiOjEs\u2026","tokenType":"Bearer","expiresAt":"2026-07-19T15:15:00+00:00","expiresIn":900,"scopes":["collections:create","transactions:read"]}}}},"403":{"description":"Mint tent\u00e9 avec un jeton, ou scopes hors p\u00e9rim\u00e8tre","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"\u00c9mettre un jeton marchand \u00e9ph\u00e9m\u00e8re (mobile \/ web)","description":"**HMAC uniquement** \u2014 un jeton ne peut pas en \u00e9mettre un autre.\n\nLa cl\u00e9 HMAC permanente ne doit JAMAIS \u00eatre embarqu\u00e9e dans une app mobile ou une page web. Le backend du marchand mint ce jeton court (TTL <= 900 s) et le remet au client, qui l'utilise en `Authorization: Bearer bt_\u2026` sur les routes \/api\/v1\/*. Les scopes demand\u00e9s sont un sous-ensemble STRICT de ceux du credential ; r\u00e9voquer le credential invalide imm\u00e9diatement tous les jetons qu'il a \u00e9mis.","requestBody":{"description":"Optionnel.","content":{"application\/json":{"schema":{"type":"object","properties":{"scopes":{"type":"array","items":{"type":"string"},"description":"Sous-ensemble des scopes du credential. D\u00e9faut : tous.","example":["collections:create","transactions:read","fees:quote"]},"ttl":{"type":"integer","maximum":900,"default":900,"description":"Dur\u00e9e de vie en secondes."}}}}},"required":false},"security":[{"HmacKey":[]}]}},"\/api\/v1\/collections":{"post":{"operationId":"merchantCollectionCreate","tags":["Marchand (HMAC)"],"responses":{"201":{"description":"Encaissement cr\u00e9\u00e9 et mis en file (TransactionResponse complet : nextAction, \u00e9ch\u00e9ance, montants d\u00e9compos\u00e9s)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"R\u00e8gle m\u00e9tier non satisfaite","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"Erreur interne","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"D\u00e9pendance indisponible (retryable)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Encaisser un paiement mobile money (type COLLECTION)","description":"Scope requis : `collections:create`. Le wallet marchand destinataire est r\u00e9solu automatiquement (cr\u00e9\u00e9 au premier encaissement). \u00c9quivaut \u00e0 POST \/v1\/transactions avec type=COLLECTION.\n\nSuivi : GET \/v1\/transactions\/{reference} jusqu'\u00e0 un statut terminal, ou webhooks (transaction.succeeded \/ transaction.failed).","parameters":[{"name":"Idempotency-Key","in":"header","description":"UUID. OBLIGATOIRE. Rejouer la m\u00eame cl\u00e9 avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; avec un corps diff\u00e9rent renvoie 409 IDEMPOTENCY_CONFLICT.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CollectionCreateRequest"}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/me":{"get":{"operationId":"merchantMe","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Identit\u00e9","content":{"application\/json":{"schema":{"type":"object"},"example":{"merchant":{"reference":"merch_01H\u2026","name":"ALBA STORE"},"application":{"reference":"app_01H\u2026","name":"ALBA STORE Mobile","context":"MOBILE_APP"},"environment":"sandbox","scopes":["collections:create"],"authMethod":"token"}}}}},"summary":"Identit\u00e9 du marchand authentifi\u00e9","description":"Fonctionne en HMAC comme en jeton \u00e9ph\u00e9m\u00e8re. Sert de sonde d'authentification au SDK.","security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/operators":{"get":{"operationId":"merchantOperators","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Op\u00e9rateurs","content":{"application\/json":{"schema":{"type":"object"},"example":{"items":[{"code":"MTN","name":"MTN Mobile Money","status":"ACTIVE","currencies":["XAF"],"capabilities":["collections","payouts"],"countryCode":"CM"},{"code":"ORANGE","name":"Orange Money","status":"ACTIVE","currencies":["XAF"],"capabilities":["collections","payouts"],"countryCode":"CM"}]}}}}},"summary":"Lister les op\u00e9rateurs mobile money disponibles","description":"Aliment\u00e9 dynamiquement (statut ACTIVE | DEGRADED | DOWN) : le s\u00e9lecteur de paiement du SDK doit masquer ou griser un op\u00e9rateur DOWN sans red\u00e9ploiement.","security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/payouts":{"post":{"operationId":"merchantPayoutCreate","tags":["Marchand (HMAC)"],"responses":{"201":{"description":"D\u00e9caissement cr\u00e9\u00e9 (hold montant + frais pos\u00e9) et mis en file","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"R\u00e8gle m\u00e9tier non satisfaite","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"Erreur interne","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"D\u00e9pendance indisponible (retryable)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"D\u00e9caisser vers un num\u00e9ro mobile money (type MERCHANT_PAYOUT)","description":"Scope requis : `payouts:create` \u2014 en contexte v1, c'est le SCOPE qui autorise, pas le r\u00f4le Dashboard (Owner\/Finance ne concernent que \/api\/dashboard\/operations). Le wallet marchand source est r\u00e9solu automatiquement ; un hold couvre montant + frais avant ex\u00e9cution.","parameters":[{"name":"Idempotency-Key","in":"header","description":"UUID. OBLIGATOIRE. Rejouer la m\u00eame cl\u00e9 avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; avec un corps diff\u00e9rent renvoie 409 IDEMPOTENCY_CONFLICT.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PayoutCreateRequest"}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions":{"get":{"operationId":"merchantTransactionList","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Liste pagin\u00e9e","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/PaginatedTransactions"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"Erreur interne","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Lister ses transactions (enveloppe pagin\u00e9e normalis\u00e9e)","description":"Scope : `transactions:read`. R\u00e9ponse {items, page, itemsPerPage, total, hasNext} \u2014 m\u00eame enveloppe que le dashboard (API-P1-009). merchantReference filtre en correspondance EXACTE.","parameters":[{"name":"status","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","enum":["CREATED","FEE_QUOTED","HELD","QUEUED","PROCESSING","PENDING_CONFIRMATION","SUCCEEDED","FAILED","CANCELLED","REVERSED","EXPIRED","PENDING_RECONCILIATION"]},"style":"form","explode":false,"allowReserved":false},{"name":"type","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","enum":["COLLECTION","WALLET_TOPUP","CASH_OUT","MERCHANT_PAYMENT","MERCHANT_PAYOUT","REFUND"]},"style":"form","explode":false,"allowReserved":false},{"name":"merchantReference","in":"query","description":"Correspondance EXACTE.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"parentReference","in":"query","description":"Refunds d'une transaction.","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"form","explode":false,"allowReserved":false},{"name":"page","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":1},"style":"form","explode":false,"allowReserved":false},{"name":"itemsPerPage","in":"query","description":"","required":false,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"integer","default":25,"maximum":100},"style":"form","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]},"post":{"operationId":"merchantTransactionCreate","tags":["Marchand (HMAC)"],"responses":{"201":{"description":"Transaction cr\u00e9\u00e9e","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"R\u00e8gle m\u00e9tier non satisfaite","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"Erreur interne","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"503":{"description":"D\u00e9pendance indisponible (retryable)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Cr\u00e9er une transaction (route g\u00e9n\u00e9rique \u2014 Idempotency-Key OBLIGATOIRE)","description":"Route g\u00e9n\u00e9rique sous-jacente \u00e0 \/v1\/collections et \/v1\/payouts. Scope : `transactions:create`.\n\n`operator` est OBLIGATOIRE pour tout type \u00e0 ex\u00e9cution mobile money. Rejouer la m\u00eame cl\u00e9 d'idempotence avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; corps diff\u00e9rent = 409.","parameters":[{"name":"Idempotency-Key","in":"header","description":"UUID. OBLIGATOIRE. Rejouer la m\u00eame cl\u00e9 avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; avec un corps diff\u00e9rent renvoie 409 IDEMPOTENCY_CONFLICT.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionCreateRequest"}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions\/by-idempotency-key\/{key}":{"get":{"operationId":"merchantTransactionByIdempotencyKey","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Transaction retrouv\u00e9e","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Recherche EXACTE par cl\u00e9 d'idempotence (r\u00e9cup\u00e9ration apr\u00e8s timeout)","description":"API-P0-014 \u2014 apr\u00e8s un timeout de cr\u00e9ation : 200 = la transaction existe (aucun rejeu n\u00e9cessaire) ; 404 = la cr\u00e9ation n'a PAS abouti, rejouer avec la M\u00caME cl\u00e9 est s\u00fbr et ne peut pas cr\u00e9er de double d\u00e9bit.","parameters":[{"name":"key","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions\/by-merchant-reference\/{reference}":{"get":{"operationId":"merchantTransactionByMerchantReference","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Transaction unique","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Recherche EXACTE par r\u00e9f\u00e9rence marchande","description":"API-P0-015 \u2014 une seule correspondance = 200 ; plusieurs = 409 MERCHANT_REFERENCE_AMBIGUOUS avec la liste des r\u00e9f\u00e9rences (utilisez alors la cl\u00e9 d'idempotence).","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions\/{reference}":{"get":{"operationId":"merchantTransactionShow","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"TransactionResponse","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"D\u00e9tail d'une transaction (scop\u00e9 au marchand)","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^txn_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions\/{reference}\/cancel":{"post":{"operationId":"merchantTransactionCancel","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Transaction annul\u00e9e","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Annuler une transaction non ex\u00e9cut\u00e9e","description":"API-P1-001 \u2014 annulable UNIQUEMENT avant ex\u00e9cution (CREATED, FEE_QUOTED, HELD, QUEUED) ; PROCESSING ne l'est pas (l'argent est peut-\u00eatre en mouvement). Lib\u00e8re les fonds r\u00e9serv\u00e9s. Idempotent : annuler une transaction d\u00e9j\u00e0 CANCELLED renvoie 200.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"description":"Optionnel.","content":{"application\/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","nullable":true}}}}},"required":false},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/transactions\/{reference}\/refund":{"post":{"operationId":"merchantRefundCreate","tags":["Marchand (HMAC)"],"responses":{"201":{"description":"Remboursement cr\u00e9\u00e9 (TransactionResponse + remainingAfter)","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"422":{"description":"R\u00e8gle m\u00e9tier non satisfaite","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"500":{"description":"Erreur interne","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Rembourser une transaction (total ou partiel)","description":"Scope requis : `refunds:create`. Remboursables : COLLECTION et MERCHANT_PAYMENT au statut SUCCEEDED.\n\nLe remboursement est une NOUVELLE transaction de type REFUND, li\u00e9e par `parentReference` ; sans `amount`, le restant est int\u00e9gralement rembours\u00e9. Restitution : re-cr\u00e9dit du wallet client (origine MERCHANT_PAYMENT) ou renvoi mobile money (origine COLLECTION). Quand la somme des refunds r\u00e9ussis atteint le montant d'origine, la transaction d'origine passe REVERSED (webhook transaction.reversed). Aucun frais sur un remboursement.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","example":"txn_01H\u2026"},"style":"simple","explode":false,"allowReserved":false},{"name":"Idempotency-Key","in":"header","description":"UUID. OBLIGATOIRE. Rejouer la m\u00eame cl\u00e9 avec le m\u00eame corps renvoie la r\u00e9ponse m\u00e9moris\u00e9e ; avec un corps diff\u00e9rent renvoie 409 IDEMPOTENCY_CONFLICT.","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","format":"uuid"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"description":"Optionnel.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/RefundCreateRequest"}}},"required":false},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/wallets\/{reference}":{"get":{"operationId":"merchantWalletShow","tags":["Marchand (HMAC)"],"responses":{"200":{"description":"Solde","content":{"application\/json":{"schema":{"type":"object"},"example":{"reference":"acct_01H\u2026","currency":"XAF","availableBalance":120000,"lockedBalance":0,"totalBalance":120000,"status":"ACTIVE"}}}},"403":{"description":"Scope manquant ou compte hors p\u00e9rim\u00e8tre","content":{"application\/json":{"schema":{"type":"object"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"\u2026","correlationId":null,"details":null}}}}}},"summary":"Solde d'un compte du marchand","description":"Scope requis : `wallets:read`. Un marchand ne peut pas lire un wallet client par ce canal.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]}]}},"\/api\/v1\/webhook-endpoints":{"get":{"operationId":"merchantWebhookList","tags":["Marchand \u2014 Webhooks"],"responses":{"200":{"description":"Endpoints","content":{"application\/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/WebhookEndpointResponse"}}}}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Lister ses endpoints de webhook (sans secret)","security":[{"HmacKey":[]},{"MerchantToken":[]}]},"post":{"operationId":"merchantWebhookCreate","tags":["Marchand \u2014 Webhooks"],"responses":{"201":{"description":"Endpoint cr\u00e9\u00e9 \u2014 secret whsec_ retourn\u00e9 UNE SEULE FOIS","content":{"application\/json":{"schema":{"type":"object"},"example":{"reference":"whk_01H\u2026","url":"https:\/\/\u2026","events":[],"secret":"whsec_\u2026"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Enregistrer un endpoint (secret whsec_ affich\u00e9 UNE fois)","description":"Scope : `webhooks:write`. HTTPS obligatoire.\n\nChaque livraison est sign\u00e9e (X-BCash-Signature = HMAC_SHA256(secret, timestamp + \"\\n\" + corps)) et envelopp\u00e9e selon le sch\u00e9ma WebhookEventEnvelope. Politique compl\u00e8te (retries, doublons, ordre) : \/v1\/meta -> webhookDelivery.\n\n`callbackUrl` \u00e0 la cr\u00e9ation d'une transaction est D\u00c9PR\u00c9CI\u00c9 : seuls les endpoints enregistr\u00e9s re\u00e7oivent des livraisons (API-P0-013).","requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","example":"https:\/\/boutique.example\/wc-api\/bcash"},"events":{"type":"array","items":{"type":"string"},"description":"Vide = tous. Liste : \/v1\/meta -> webhookEvents."}},"required":["url"]}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/webhook-endpoints\/{reference}":{"get":{"operationId":"merchantWebhookShow","tags":["Marchand \u2014 Webhooks"],"responses":{"200":{"description":"Endpoint","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WebhookEndpointResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"D\u00e9tail d'un endpoint (sans secret, avec lastDeliveryAt \/ lastFailureAt)","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]},"delete":{"operationId":"merchantWebhookDelete","tags":["Marchand \u2014 Webhooks"],"responses":{"204":{"description":"Supprim\u00e9"},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Supprimer un endpoint (douce : historique conserv\u00e9)","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]},"patch":{"operationId":"merchantWebhookPatch","tags":["Marchand \u2014 Webhooks"],"responses":{"200":{"description":"Endpoint mis \u00e0 jour","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WebhookEndpointResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Modifier URL et\/ou \u00e9v\u00e9nements","description":"R\u00e9pare une configuration SANS cr\u00e9er d'endpoint orphelin (API-P0-012).","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}}}}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/webhook-endpoints\/{reference}\/disable":{"post":{"operationId":"merchantWebhookDisable","tags":["Marchand \u2014 Webhooks"],"responses":{"200":{"description":"Endpoint suspendu","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WebhookEndpointResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Suspendre un endpoint (aucune livraison)","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/webhook-endpoints\/{reference}\/enable":{"post":{"operationId":"merchantWebhookEnable","tags":["Marchand \u2014 Webhooks"],"responses":{"200":{"description":"Endpoint actif","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/WebhookEndpointResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"R\u00e9activer un endpoint","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/webhook-endpoints\/{reference}\/rotate-secret":{"post":{"operationId":"merchantWebhookRotate","tags":["Marchand \u2014 Webhooks"],"responses":{"201":{"description":"Nouveau secret (affich\u00e9 une seule fois) + \u00e9ch\u00e9ance de l'ancien"},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"Faire tourner le secret AVEC chevauchement 24 h","description":"API-P1-002 \u2014 le nouveau secret signe imm\u00e9diatement (X-BCash-Signature) et l'ancien co-signe (X-BCash-Signature-Previous) pendant 24 h : validez l'une OU l'autre, aucune livraison rat\u00e9e.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/webhook-endpoints\/{reference}\/test":{"post":{"operationId":"merchantWebhookTest","tags":["Marchand \u2014 Webhooks"],"responses":{"202":{"description":"Test mis en file (eventId)"},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"\u00c9mettre un \u00e9v\u00e9nement webhook.test r\u00e9el","description":"API-P1-013 \u2014 passe par le pipeline normal (signature, retries). V\u00e9rifiez ensuite lastDeliveryAt.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"style":"simple","explode":false,"allowReserved":false}],"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/sandbox\/transactions\/{reference}\/advance":{"post":{"operationId":"sandboxAdvance","tags":["Sandbox"],"responses":{"200":{"description":"Transaction r\u00e9solue","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/TransactionResponse"}}}},"400":{"description":"Requ\u00eate invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"401":{"description":"Authentification invalide","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"403":{"description":"Scope ou p\u00e9rim\u00e8tre insuffisant","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"404":{"description":"Ressource introuvable","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}},"409":{"description":"Conflit","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorResponse"}}}}},"summary":"SANDBOX \u2014 forcer l'issue d'une transaction non terminale","description":"API-P0-018 \u2014 m\u00eames effets que le chemin nominal (ledger, webhooks, audit). R\u00e9serv\u00e9 aux credentials sandbox (403 en production). Sc\u00e9narios : \/v1\/meta -> sandbox.","parameters":[{"name":"reference","in":"path","description":"","required":true,"deprecated":false,"allowEmptyValue":false,"schema":{"type":"string"},"style":"simple","explode":false,"allowReserved":false}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["SUCCEEDED","FAILED","EXPIRED"]},"failureCode":{"type":"string","nullable":true}},"required":["status"]}}},"required":true},"security":[{"HmacKey":[]},{"MerchantToken":[]}]}},"\/api\/v1\/health":{"get":{"operationId":"health","tags":["Sant\u00e9"],"responses":{"200":{"description":"OK","content":{"application\/json":{"schema":{"type":"object"},"example":{"status":"ok","service":"bcash-api"}}}}},"summary":"Sonde de sant\u00e9 (public)","security":[]}},"\/api\/v1\/meta":{"get":{"operationId":"apiMeta","tags":["Sant\u00e9"],"responses":{"200":{"description":"Contrat"}},"summary":"Contrat public stable (\u00e9num\u00e9rations, \u00e9v\u00e9nements, signature webhook)","description":"Public. \u00c9num\u00e9rations status\/type, liste EXHAUSTIVE des \u00e9v\u00e9nements de webhook, sp\u00e9cification de signature, r\u00e8gles d'idempotence et de devis. Les clients doivent ignorer les valeurs inconnues (des valeurs peuvent \u00eatre ajout\u00e9es, jamais retir\u00e9es en 1.x).","security":[]}}},"components":{"schemas":{"FeeQuote":{"type":"object","description":"","deprecated":false,"required":["type","context","amount"],"properties":{"type":{"type":"string"},"context":{"type":"string"},"amount":{"exclusiveMinimum":0,"type":["integer","null"]},"currency":{"default":"XAF","example":"XAF","type":"string"},"operator":{"type":["string","null"]},"amountBasis":{"default":"GROSS","example":"GROSS","type":"string"},"reference":{"type":["string","null"]},"orderAmount":{"type":["integer","null"]},"customerFeeAmount":{"type":["integer","null"]},"customerPayableAmount":{"type":["integer","null"]},"merchantFeeAmount":{"type":["integer","null"]},"merchantNetAmount":{"type":["integer","null"]},"platformFeeAmount":{"type":["integer","null"]},"feePayer":{"type":["string","null"]},"ruleVersion":{"type":["string","null"]},"expiresAt":{"type":["string","null"]},"consumedAt":{"type":["string","null"]},"feeAmount":{"type":["integer","null"]},"netAmount":{"type":["integer","null"]},"payer":{"type":["string","null"]}}},"Transaction":{"type":"object","description":"","deprecated":false,"required":["type","context"],"properties":{"type":{"type":"string","enum":["COLLECTION","WALLET_TOPUP","CASH_OUT","MERCHANT_PAYMENT","MERCHANT_PAYOUT","REFUND"]},"context":{"type":"string"},"amount":{"exclusiveMinimum":0,"type":"integer"},"currency":{"default":"XAF","example":"XAF","type":"string"},"customer":{"type":"object","nullable":true,"properties":{"phone":{"type":"string","example":"+237650000000"},"displayName":{"type":"string","nullable":true}},"required":["phone"]},"merchantReference":{"type":["string","null"]},"callbackUrl":{"type":["string","null"]},"operator":{"type":["string","null"]},"feeQuoteId":{"type":["string","null"]},"sourceWalletId":{"type":["string","null"]},"destinationWalletId":{"type":["string","null"]},"metadata":{"type":"array","items":{"type":"string"}},"reference":{"type":["string","null"]},"status":{"type":"string","enum":["CREATED","FEE_QUOTED","HELD","QUEUED","PROCESSING","PENDING_CONFIRMATION","SUCCEEDED","FAILED","CANCELLED","REVERSED","EXPIRED","PENDING_RECONCILIATION"],"description":"\u00c9num\u00e9ration stable \u2014 cf. GET \/api\/v1\/meta.","nullable":true},"feeAmount":{"type":["integer","null"]},"netAmount":{"type":["integer","null"]},"createdAt":{"type":["string","null"]}}},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Code machine stable \u2014 catalogue complet : \/v1\/meta -> errors.catalog"},"message":{"type":"string"},"correlationId":{"type":"string"},"retryable":{"type":"boolean","description":"true = rejouer la m\u00eame requ\u00eate (m\u00eame Idempotency-Key) peut r\u00e9ussir."},"retryAfter":{"type":["integer","null"],"description":"D\u00e9lai conseill\u00e9 (s) avant retry."},"details":{"type":"object"}},"required":["code","message","correlationId","retryable"]}}},"NextAction":{"type":["object","null"],"properties":{"type":{"type":"string","enum":["AWAIT_MOBILE_CONFIRMATION","AWAIT_PROCESSING"]},"displayMessage":{"type":"string","description":"Message pr\u00eat \u00e0 afficher sur la page d'attente."},"expiresAt":{"type":["string","null"],"format":"date-time"}}},"TransactionResponse":{"type":"object","description":"Repr\u00e9sentation UNIQUE d'une transaction \u2014 identique dans les lectures, les cr\u00e9ations et data.object des webhooks.","properties":{"reference":{"type":"string","pattern":"^txn_[A-Za-z0-9]+$"},"type":{"type":"string","enum":["COLLECTION","WALLET_TOPUP","CASH_OUT","MERCHANT_PAYMENT","MERCHANT_PAYOUT","REFUND"]},"context":{"type":"string"},"status":{"type":"string","enum":["CREATED","FEE_QUOTED","HELD","QUEUED","PROCESSING","PENDING_CONFIRMATION","SUCCEEDED","FAILED","CANCELLED","REVERSED","EXPIRED","PENDING_RECONCILIATION"],"description":"\u00c9num\u00e9ration stable \u2014 cf. GET \/api\/v1\/meta.","nullable":true},"isTerminal":{"type":"boolean"},"statusReason":{"type":["string","null"]},"amount":{"type":"integer","description":"XAF entier (brut mouvement\u00e9)."},"currency":{"type":"string"},"operator":{"type":["string","null"]},"operatorReference":{"type":["string","null"],"description":"R\u00e9f\u00e9rence renvoy\u00e9e par l'op\u00e9rateur."},"merchantReference":{"type":["string","null"]},"parentReference":{"type":["string","null"],"description":"Origine pour un REFUND."},"feeQuoteReference":{"type":["string","null"]},"customer":{"type":"object","nullable":true,"properties":{"phone":{"type":"string","example":"+237650000000"},"displayName":{"type":"string","nullable":true}},"required":["phone"]},"failureCode":{"type":["string","null"]},"failureMessage":{"type":["string","null"]},"failureCategory":{"type":["string","null"],"enum":["VALIDATION","AUTH","NOT_FOUND","CONFLICT","STATE","BUSINESS","UPSTREAM","INTERNAL",null]},"retryable":{"type":["boolean","null"]},"refundedAmount":{"type":["integer","null"],"description":"Cumul rembours\u00e9 (types remboursables)."},"refundableAmount":{"type":["integer","null"],"description":"Restant remboursable."},"metadata":{"type":"object","additionalProperties":{"type":["string","number","integer","boolean","null"]},"description":"Objet PLAT, <= 32 cl\u00e9s, <= 4096 octets, pr\u00e9fixe \u00ab bcash_ \u00bb r\u00e9serv\u00e9. Visible au dashboard et dans les webhooks : aucune donn\u00e9e sensible. Cf. \/v1\/meta -> metadata."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"processedAt":{"type":["string","null"],"format":"date-time"},"succeededAt":{"type":["string","null"],"format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time","description":"\u00c9ch\u00e9ance de confirmation (collections)."},"nextAction":{"$ref":"#\/components\/schemas\/NextAction"},"correlationId":{"type":["string","null"]},"orderAmount":{"type":"integer","description":"Montant de la commande."},"customerFeeAmount":{"type":"integer","description":"Frais pay\u00e9s EN SUS par le client (0 si absorb\u00e9s)."},"customerPayableAmount":{"type":"integer","description":"TOTAL d\u00e9bit\u00e9 du mobile money du client."},"merchantFeeAmount":{"type":"integer","description":"Frais \u00e0 la charge du marchand (0 si en sus)."},"merchantNetAmount":{"type":"integer","description":"Ce que le wallet marchand re\u00e7oit."},"platformFeeAmount":{"type":"integer","description":"Total des frais plateforme."},"feePayer":{"type":"string","enum":["CUSTOMER","MERCHANT"]}},"required":["reference","type","status","isTerminal","amount","currency","createdAt","updatedAt"]},"CollectionCreateRequest":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"description":"XAF entier. Avec feeQuoteId : DOIT \u00e9galer customerPayableAmount de la quote."},"currency":{"type":"string","default":"XAF"},"operator":{"type":"string","description":"OBLIGATOIRE \u2014 code de GET \/v1\/operators.","example":"MTN"},"customer":{"type":"object","nullable":true,"properties":{"phone":{"type":"string","example":"+237650000000"},"displayName":{"type":"string","nullable":true}},"required":["phone"]},"merchantReference":{"type":["string","null"],"example":"WC-ORDER-1042"},"feeQuoteId":{"type":["string","null"]},"metadata":{"type":"object","additionalProperties":{"type":["string","number","integer","boolean","null"]},"description":"Objet PLAT, <= 32 cl\u00e9s, <= 4096 octets, pr\u00e9fixe \u00ab bcash_ \u00bb r\u00e9serv\u00e9. Visible au dashboard et dans les webhooks : aucune donn\u00e9e sensible. Cf. \/v1\/meta -> metadata."},"context":{"type":"string","default":"MOBILE_APP","example":"WOOCOMMERCE"},"callbackUrl":{"type":["string","null"],"deprecated":true,"description":"D\u00c9PR\u00c9CI\u00c9 (API-P0-013) : accept\u00e9 pour compatibilit\u00e9 mais JAMAIS utilis\u00e9 pour livrer. Utilisez les webhook endpoints enregistr\u00e9s."}},"required":["amount","operator","customer"]},"PayoutCreateRequest":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"description":"Montant RE\u00c7U par le b\u00e9n\u00e9ficiaire ; le wallet est d\u00e9bit\u00e9 de amount + frais."},"currency":{"type":"string","default":"XAF"},"operator":{"type":"string","description":"OBLIGATOIRE."},"recipient":{"type":"object","properties":{"phone":{"type":"string","example":"+237690000000"},"name":{"type":["string","null"]}},"required":["phone"]},"merchantReference":{"type":["string","null"]},"feeQuoteId":{"type":["string","null"]},"metadata":{"type":"object","additionalProperties":{"type":["string","number","integer","boolean","null"]},"description":"Objet PLAT, <= 32 cl\u00e9s, <= 4096 octets, pr\u00e9fixe \u00ab bcash_ \u00bb r\u00e9serv\u00e9. Visible au dashboard et dans les webhooks : aucune donn\u00e9e sensible. Cf. \/v1\/meta -> metadata."}},"required":["amount","operator","recipient"]},"RefundCreateRequest":{"type":"object","properties":{"amount":{"type":["integer","null"],"description":"D\u00e9faut : restant remboursable (refundableAmount)."},"reason":{"type":["string","null"]},"merchantReference":{"type":["string","null"]}}},"TransactionCreateRequest":{"type":"object","description":"Route g\u00e9n\u00e9rique \u2014 pr\u00e9f\u00e9rez \/v1\/collections et \/v1\/payouts, qui fixent le type et r\u00e9solvent le wallet marchand.","properties":{"type":{"type":"string","enum":["COLLECTION","WALLET_TOPUP","CASH_OUT","MERCHANT_PAYMENT","MERCHANT_PAYOUT","REFUND"]},"context":{"type":"string","default":"API_DIRECT"},"amount":{"type":"integer","minimum":1},"currency":{"type":"string","default":"XAF"},"operator":{"type":["string","null"],"description":"OBLIGATOIRE pour tout type \u00e0 ex\u00e9cution mobile money."},"customer":{"type":"object","nullable":true,"properties":{"phone":{"type":"string","example":"+237650000000"},"displayName":{"type":"string","nullable":true}},"required":["phone"]},"merchantReference":{"type":["string","null"]},"feeQuoteId":{"type":["string","null"]},"sourceWalletId":{"type":["string","null"]},"destinationWalletId":{"type":["string","null"]},"metadata":{"type":"object","additionalProperties":{"type":["string","number","integer","boolean","null"]},"description":"Objet PLAT, <= 32 cl\u00e9s, <= 4096 octets, pr\u00e9fixe \u00ab bcash_ \u00bb r\u00e9serv\u00e9. Visible au dashboard et dans les webhooks : aucune donn\u00e9e sensible. Cf. \/v1\/meta -> metadata."},"callbackUrl":{"type":["string","null"],"deprecated":true}},"required":["type","amount"]},"FeeQuoteCreateRequest":{"type":"object","properties":{"type":{"type":"string","enum":["COLLECTION","WALLET_TOPUP","CASH_OUT","MERCHANT_PAYMENT","MERCHANT_PAYOUT","REFUND"]},"context":{"type":"string","example":"WOOCOMMERCE"},"amount":{"type":"integer","minimum":1,"description":"OBLIGATOIRE. Sens fix\u00e9 par amountBasis."},"currency":{"type":"string","default":"XAF"},"operator":{"type":["string","null"],"description":"Recommand\u00e9 pour un encaissement (r\u00e8gles par op\u00e9rateur)."},"amountBasis":{"type":"string","enum":["GROSS","ORDER_NET"],"default":"GROSS","description":"GROSS : amount = d\u00e9bit\u00e9 au client, frais absorb\u00e9s. ORDER_NET : amount = net commande, frais EN SUS (gross-up exact)."}},"required":["type","context","amount"]},"FeeQuoteResponse":{"type":"object","properties":{"reference":{"type":"string","pattern":"^feeq_[A-Za-z0-9]+$"},"ruleVersion":{"type":["string","null"]},"expiresAt":{"type":"string","format":"date-time","description":"\u00c9ch\u00e9ance r\u00e9elle (TTL 900 s)."},"consumedAt":{"type":["string","null"],"format":"date-time"},"orderAmount":{"type":"integer","description":"Montant de la commande."},"customerFeeAmount":{"type":"integer","description":"Frais pay\u00e9s EN SUS par le client (0 si absorb\u00e9s)."},"customerPayableAmount":{"type":"integer","description":"TOTAL d\u00e9bit\u00e9 du mobile money du client."},"merchantFeeAmount":{"type":"integer","description":"Frais \u00e0 la charge du marchand (0 si en sus)."},"merchantNetAmount":{"type":"integer","description":"Ce que le wallet marchand re\u00e7oit."},"platformFeeAmount":{"type":"integer","description":"Total des frais plateforme."},"feePayer":{"type":"string","enum":["CUSTOMER","MERCHANT"]}},"required":["reference","expiresAt","customerPayableAmount","merchantNetAmount","platformFeeAmount","feePayer"]},"WebhookEndpointResponse":{"type":"object","description":"Endpoint SANS son secret (seul secretLast4 est re-consultable).","properties":{"reference":{"type":"string","pattern":"^whk_[A-Za-z0-9]+$"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"},"description":"Vide = tous les \u00e9v\u00e9nements."},"status":{"type":"string","enum":["CREATED","FEE_QUOTED","HELD","QUEUED","PROCESSING","PENDING_CONFIRMATION","SUCCEEDED","FAILED","CANCELLED","REVERSED","EXPIRED","PENDING_RECONCILIATION"],"description":"\u00c9num\u00e9ration stable \u2014 cf. GET \/api\/v1\/meta.","nullable":true},"secretLast4":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"},"lastDeliveryAt":{"type":["string","null"],"format":"date-time"},"lastFailureAt":{"type":["string","null"],"format":"date-time"},"previousSecretValidUntil":{"type":["string","null"],"format":"date-time"}}},"WebhookEventEnvelope":{"type":"object","description":"Enveloppe LIVR\u00c9E sur vos endpoints (API-P0-010). data.object = TransactionResponse.","properties":{"id":{"type":"string","pattern":"^evt_[A-Za-z0-9]+$","description":"Stable entre les tentatives \u2014 cl\u00e9 de d\u00e9duplication."},"type":{"type":"string","enum":["transaction.created","transaction.succeeded","transaction.failed","transaction.pending_confirmation","transaction.reversed","transaction.expired","transaction.pending_reconciliation","webhook.test"]},"apiVersion":{"type":"string","example":"2026-07-20"},"createdAt":{"type":"string","format":"date-time"},"livemode":{"type":"boolean"},"data":{"type":"object","properties":{"object":{"$ref":"#\/components\/schemas\/TransactionResponse"},"previousAttributes":{"type":"object"}}}},"required":["id","type","apiVersion","createdAt","livemode","data"]},"PaginatedTransactions":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/TransactionResponse"}},"page":{"type":"integer"},"itemsPerPage":{"type":"integer"},"total":{"type":"integer"},"hasNext":{"type":"boolean"}},"required":["items","page","itemsPerPage","total","hasNext"]}},"responses":{},"parameters":{},"examples":{},"requestBodies":{},"headers":{},"securitySchemes":{"JWT":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"MerchantToken":{"type":"http","scheme":"bearer","description":"Jeton marchand \u00e9ph\u00e9m\u00e8re (bt_\u2026, TTL <= 900 s) \u00e9mis par POST \/api\/v1\/auth\/token. Pour le SDK mobile\/web \u2014 jamais la cl\u00e9 HMAC dans une app."},"HmacKey":{"type":"apiKey","in":"header","name":"X-BCash-Key","description":"Applications marchandes. \u00c0 accompagner de X-BCash-Timestamp et X-BCash-Signature = HMAC_SHA256(secret, \"METHOD\\nPATH\\nTIMESTAMP\\nsha256_hex(body)\")."},"HubDeviceSignature":{"type":"apiKey","in":"header","name":"X-BCash-Device","description":"Terminaux du HUB. \u00c0 accompagner de X-BCash-Timestamp et X-BCash-Signature = base64(ECDSA_P256_SHA256(cl\u00e9_priv\u00e9e_keystore, \"METHOD\\nPATH\\nTIMESTAMP\\nsha256_hex(body)\")). La cl\u00e9 priv\u00e9e est g\u00e9n\u00e9r\u00e9e dans le Keystore mat\u00e9riel et n'est pas exportable ; le serveur ne d\u00e9tient que la cl\u00e9 publique. Fen\u00eatre de 300 s tol\u00e9r\u00e9e sur l'horodatage. Anti-rejeu : une m\u00eame signature n'est accept\u00e9e qu'une fois par fen\u00eatre de 2 \u00d7 300 s."}}},"security":[],"tags":[],"webhooks":{}}