{
  "openapi": "3.1.0",
  "info": {
    "title": "FastCash API Partenaires",
    "version": "1.1.0",
    "summary": "Dépôts, retraits, transactions et commissions partenaires.",
    "description": "API B2B exclusivement serveur-à-serveur. La clé API et le secret webhook sont remis une seule fois lors de la création du partenaire. Ne placez jamais ces secrets dans un navigateur, une application mobile, une URL ou un dépôt Git.\n\nUne réponse HTTP 202 confirme l’acceptation d’un dépôt ou retrait, pas son achèvement. Suivez le statut par webhook ou avec GET /transactions/{id}.\n\nIdempotence v1 : partner_external_id est tracé, mais les doublons ne sont pas bloqués automatiquement. Utilisez une référence unique. Ne relancez jamais automatiquement POST /deposits ou POST /withdrawals après un timeout, une erreur 5xx ou un résultat ambigu.",
    "contact": {
      "name": "Support intégration FastCash",
      "url": "https://fastcash-app.com/"
    },
    "license": {
      "name": "Propriétaire — FastCash",
      "url": "https://fastcash-app.com/"
    }
  },
  "externalDocs": {
    "description": "Site officiel FastCash",
    "url": "https://fastcash-app.com/"
  },
  "servers": [
    {
      "url": "https://gkpjwwtalnyrpktfngbc.supabase.co/functions/v1/partners-api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Référentiel",
      "description": "Bookmakers et moyens de paiement Mobile Money disponibles."
    },
    {
      "name": "Vérification de compte",
      "description": "Étape de pré-vérification recommandée avant tout dépôt ou retrait."
    },
    {
      "name": "Paiements",
      "description": "Création asynchrone de dépôts et retraits."
    },
    {
      "name": "Transactions",
      "description": "Consultation des opérations appartenant au partenaire."
    },
    {
      "name": "Commissions",
      "description": "Génération du mois courant et historique des commissions du partenaire."
    }
  ],
  "paths": {
    "/bookmakers": {
      "get": {
        "operationId": "listBookmakers",
        "tags": ["Référentiel"],
        "summary": "Lister les bookmakers et moyens de paiement Mobile Money",
        "description": "Retourne deux listes : `bookmakers` contient les plateformes de pari et leurs capacités ; `operators` contient exclusivement les moyens de paiement Mobile Money disponibles. La configuration de référence comprend Orange Money, Telecel Money, Moov Money et Wave. Utilisez le `deposit_code` de l’opérateur dans POST /deposits et son `withdrawal_code` dans POST /withdrawals. La réponse reçue au moment de l’appel est la seule source de vérité : respectez toujours `supports_deposit` et `supports_withdrawal`. Les frais bookmaker sont exprimés en fraction : 0.02 représente 2 %.",
        "responses": {
          "200": {
            "description": "Référentiel disponible.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookmakersResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "bookmakers": [
                      {
                        "id": 1,
                        "name": "1XBET",
                        "client_deposit_enabled": true,
                        "client_withdrawal_enabled": true,
                        "minimum_deposit": 100,
                        "deposit_fees": 0,
                        "withdrawal_fees": 0.01,
                        "logo_url": "https://cdn.example.com/1xbet.png",
                        "withdrawal_city": "Ouagadougou",
                        "withdrawal_address": "Agence centrale"
                      }
                    ],
                    "operators": [
                      {
                        "id": 1,
                        "name": "Orange Money",
                        "deposit_code": "ORANGE_BFA_MARCHAND",
                        "withdrawal_code": "ORANGE_BFA_CASHIN",
                        "phone_prefixes": ["04", "05", "06", "07", "44", "45", "46", "54", "55", "56", "57", "64", "65", "66", "67", "74", "75", "76", "77"],
                        "icon_url": null,
                        "supports_deposit": true,
                        "supports_withdrawal": true
                      },
                      {
                        "id": 2,
                        "name": "Telecel Money",
                        "deposit_code": "TELECEL_BFA_MARCHAND",
                        "withdrawal_code": "TELECEL_BFA_CASHIN",
                        "phone_prefixes": ["58", "68", "69", "78", "79"],
                        "icon_url": null,
                        "supports_deposit": true,
                        "supports_withdrawal": true
                      },
                      {
                        "id": 3,
                        "name": "Moov Money",
                        "deposit_code": "MOOV_BFA_MARCHAND",
                        "withdrawal_code": "MOOV_BFA_CASHIN",
                        "phone_prefixes": ["01", "02", "03", "50", "51", "52", "53", "60", "61", "62", "63", "70", "71", "72", "73"],
                        "icon_url": null,
                        "supports_deposit": true,
                        "supports_withdrawal": true
                      },
                      {
                        "id": 4,
                        "name": "Wave",
                        "deposit_code": "WAVE_BFA_MARCHAND",
                        "withdrawal_code": "WAVE_BFA_CASHIN",
                        "phone_prefixes": null,
                        "icon_url": null,
                        "supports_deposit": true,
                        "supports_withdrawal": false
                      }
                    ]
                  },
                  "timestamp": "2026-07-30T12:00:00.000Z",
                  "requestId": "f0a61ac6-c502-49d1-a17f-85c7e56d6e08"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/bookmakers/{id}/balance": {
      "get": {
        "operationId": "getBookmakerBalance",
        "tags": ["Référentiel"],
        "summary": "Consulter le solde live de la caisse bookmaker",
        "description": "Lit le solde en direct auprès de Servcul. `balance` correspond au solde réel de la caisse (Limit) et `withdrawable` au montant disponible pour les retraits (Balance), en XOF. Certains bookmakers ne prennent pas en charge ce contrôle et renvoient 503.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifiant retourné par GET /bookmakers.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Solde de caisse disponible.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/ResponseMetadata" },
                    {
                      "type": "object",
                      "required": ["data"],
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": ["bookmaker_platform_id", "name", "balance", "withdrawable", "currency", "as_of"],
                          "properties": {
                            "bookmaker_platform_id": { "type": "integer" },
                            "name": { "type": "string" },
                            "balance": { "type": "number" },
                            "withdrawable": { "type": "number" },
                            "currency": { "type": "string", "const": "XOF" },
                            "as_of": { "type": "string", "format": "date-time" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "503": { "$ref": "#/components/responses/Unavailable" },
          "504": { "$ref": "#/components/responses/UpstreamTimeout" }
        }
      }
    },
    "/accounts/lookup": {
      "post": {
        "operationId": "lookupBookmakerAccount",
        "tags": ["Vérification de compte"],
        "summary": "Vérifier l’existence d’un compte bookmaker avant une opération",
        "description": "**Appelez cet endpoint avant chaque dépôt ou retrait.** Il distingue trois cas : (1) `exists: true` et `supports_lookup: true`, le compte a été confirmé ; (2) `exists: false` et `supports_lookup: true`, bloquez l’opération et affichez `reason` ; (3) `supports_lookup: false`, la plateforme ne permet pas de pré-vérification — ne bloquez pas l’utilisateur et laissez POST /deposits ou POST /withdrawals effectuer la validation finale.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountLookupRequest"
              },
              "example": {
                "bookmaker_platform_id": 1,
                "bookmaker_user_id": 12345678
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Résultat de la vérification.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountLookupResponse"
                },
                "examples": {
                  "accountConfirmed": {
                    "summary": "Compte confirmé",
                    "value": {
                      "success": true,
                      "data": {
                        "exists": true,
                        "supports_lookup": true,
                        "bookmaker_platform_id": 1,
                        "bookmaker_user_id": 12345678,
                        "account_holder_name": "Awa Ouedraogo",
                        "currency_id": 1,
                        "reason": null
                      },
                      "timestamp": "2026-07-30T12:00:00.000Z",
                      "requestId": "0bdbfa5d-9b5a-439d-bc0e-dd9df50cb21e"
                    }
                  },
                  "accountNotFound": {
                    "summary": "Compte introuvable — bloquer l’opération",
                    "value": {
                      "success": true,
                      "data": {
                        "exists": false,
                        "supports_lookup": true,
                        "bookmaker_platform_id": 1,
                        "bookmaker_user_id": 99999999,
                        "account_holder_name": null,
                        "currency_id": null,
                        "reason": "Account not found."
                      },
                      "timestamp": "2026-07-30T12:00:00.000Z",
                      "requestId": "24702a13-e274-4879-9196-1ac338292e1d"
                    }
                  },
                  "lookupUnsupported": {
                    "summary": "Pré-vérification indisponible — continuer le parcours",
                    "value": {
                      "success": true,
                      "data": {
                        "exists": true,
                        "supports_lookup": false,
                        "bookmaker_platform_id": 8,
                        "bookmaker_user_id": 12345678,
                        "account_holder_name": null,
                        "currency_id": null,
                        "reason": "Bookmaker does not support account pre-validation."
                      },
                      "timestamp": "2026-07-30T12:00:00.000Z",
                      "requestId": "8812bb66-8a90-42c1-b974-b3f6eabf28e4"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/UpstreamTimeout"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/deposits": {
      "post": {
        "operationId": "createDeposit",
        "tags": ["Paiements"],
        "summary": "Créer un dépôt",
        "description": "Crée une demande de dépôt. Une réponse 202 signifie que la demande a été acceptée. Présentez ussd_code au client lorsqu’il est renseigné ; pour Wave, utilisez wave_message. En l’absence d’idempotence forte en v1, ne relancez jamais automatiquement cette requête après un timeout, une erreur 5xx ou un résultat ambigu.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DepositRequest"
              },
              "example": {
                "bookmaker_platform_id": 1,
                "bookmaker_user_id": 12345678,
                "amount": 5000,
                "operator_code": "ORANGE_BFA_MARCHAND",
                "phone_number": "+22670000000",
                "partner_external_id": "deposit-20260730-0001",
                "user_external_id": "customer-42"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Dépôt accepté et en cours de traitement.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/UpstreamTimeout"
          }
        }
      }
    },
    "/withdrawals": {
      "post": {
        "operationId": "createWithdrawal",
        "tags": ["Paiements"],
        "summary": "Créer un retrait",
        "description": "Valide et débite le bookmaker avant le cash-out Mobile Money. Le code opérateur doit être le withdrawal_code du référentiel. Si le montant libéré par le bookmaker diffère du montant annoncé, le montant réel est utilisé. Ne relancez jamais automatiquement un retrait après un timeout, une erreur 5xx ou un résultat ambigu : gelez l’opération et contactez le support avec requestId et partner_external_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WithdrawalRequest"
              },
              "example": {
                "bookmaker_platform_id": 1,
                "bookmaker_user_id": 12345678,
                "amount": 5000,
                "withdrawal_code": "839201",
                "operator_code": "ORANGE_BFA_CASHIN",
                "phone_number": "+22670000000",
                "partner_external_id": "withdrawal-20260730-0001",
                "user_external_id": "customer-42"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Retrait accepté et en cours de traitement.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/UpstreamTimeout"
          }
        }
      }
    },
    "/transactions/{id}": {
      "get": {
        "operationId": "getTransaction",
        "tags": ["Transactions"],
        "summary": "Consulter une transaction",
        "description": "Retourne uniquement une transaction appartenant au partenaire authentifié.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransactionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Transaction trouvée.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/transactions": {
      "get": {
        "operationId": "listTransactions",
        "tags": ["Transactions"],
        "summary": "Lister les transactions",
        "description": "Retourne les transactions du partenaire de la plus récente à la plus ancienne.",
        "parameters": [
          {
            "name": "user_external_id",
            "in": "query",
            "required": false,
            "description": "Votre référence utilisateur.",
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "example": "customer-42"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Date minimale appliquée à created_at.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Date maximale appliquée à created_at.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "50 par défaut, plafonné à 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste des transactions.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/commissions": {
      "get": {
        "operationId": "listPartnerCommissions",
        "tags": ["Commissions"],
        "summary": "Générer le mois courant et lister les commissions",
        "description": "Génère ou actualise idempotemment la commission du partenaire authentifié pour le mois UTC courant, puis retourne ce mois, les agrégats et tout l’historique mensuel du plus récent au plus ancien. Les commissions dont le versement est en cours ou terminé restent figées. Si aucun volume éligible n’existe ce mois-ci, `current_period_status` vaut `no_eligible_volume` et `current_commission` vaut `null`.",
        "responses": {
          "200": {
            "description": "Commissions actualisées et disponibles.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerCommissionsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "webhooks": {
    "transactionEvent": {
      "post": {
        "operationId": "receiveTransactionEvent",
        "summary": "Recevoir un événement terminal",
        "description": "FastCash envoie transaction.completed, transaction.failed ou transaction.cancelled. Vérifiez Fastbet-Signature sur le corps brut avant de parser le JSON, refusez les timestamps vieux de plus de cinq minutes et dédupliquez durablement sur event_id. Une livraison en échec est tentée au maximum 8 fois au total avec backoff exponentiel. Les noms Fastbet-Signature et X-Fastbet-* sont conservés pour compatibilité protocolaire.",
        "security": [],
        "parameters": [
          {
            "name": "Fastbet-Signature",
            "in": "header",
            "required": true,
            "description": "t=<timestamp>,v1=<HMAC-SHA256 hex de timestamp.corps_brut>",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Fastbet-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Fastbet-Event-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "transaction.completed",
                "transaction.failed",
                "transaction.cancelled"
              ]
            }
          },
          {
            "name": "X-Fastbet-Request-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Événement validé, enregistré et traité."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "fb_live_…",
        "description": "Clé API partenaire transmise uniquement depuis votre backend."
      }
    },
    "headers": {
      "RequestId": {
        "description": "Identifiant de corrélation à conserver dans vos logs.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "parameters": {
      "TransactionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identifiant FastCash de la transaction.",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "example": 98765
      }
    },
    "schemas": {
      "ResponseMetadata": {
        "type": "object",
        "required": ["success", "timestamp", "requestId"],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "requestId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Bookmaker": {
        "type": "object",
        "required": [
          "id",
          "name",
          "client_deposit_enabled",
          "client_withdrawal_enabled",
          "minimum_deposit",
          "deposit_fees",
          "withdrawal_fees",
          "logo_url",
          "withdrawal_city",
          "withdrawal_address"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "name": {
            "type": "string"
          },
          "client_deposit_enabled": {
            "type": "boolean"
          },
          "client_withdrawal_enabled": {
            "type": "boolean"
          },
          "minimum_deposit": {
            "type": "number",
            "minimum": 0,
            "description": "Montant en XOF."
          },
          "deposit_fees": {
            "type": "number",
            "minimum": 0,
            "description": "Fraction, par exemple 0.02 pour 2 %."
          },
          "withdrawal_fees": {
            "type": "number",
            "minimum": 0,
            "description": "Fraction, par exemple 0.02 pour 2 %."
          },
          "logo_url": {
            "type": ["string", "null"],
            "format": "uri"
          },
          "withdrawal_city": {
            "type": ["string", "null"]
          },
          "withdrawal_address": {
            "type": ["string", "null"]
          }
        }
      },
      "Operator": {
        "type": "object",
        "required": [
          "id",
          "name",
          "deposit_code",
          "withdrawal_code",
          "phone_prefixes",
          "icon_url",
          "supports_deposit",
          "supports_withdrawal"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "name": {
            "type": "string"
          },
          "deposit_code": {
            "type": "string"
          },
          "withdrawal_code": {
            "type": "string"
          },
          "phone_prefixes": {
            "type": ["array", "null"],
            "items": {
              "type": "string"
            }
          },
          "icon_url": {
            "type": ["string", "null"],
            "format": "uri"
          },
          "supports_deposit": {
            "type": "boolean"
          },
          "supports_withdrawal": {
            "type": "boolean"
          }
        }
      },
      "BookmakersData": {
        "type": "object",
        "required": ["bookmakers", "operators"],
        "properties": {
          "bookmakers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bookmaker"
            }
          },
          "operators": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Operator"
            }
          }
        }
      },
      "BookmakersResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ResponseMetadata"
          },
          {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/BookmakersData"
              }
            }
          }
        ]
      },
      "AccountLookupRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["bookmaker_platform_id", "bookmaker_user_id"],
        "properties": {
          "bookmaker_platform_id": {
            "type": "integer",
            "minimum": 1
          },
          "bookmaker_user_id": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "AccountLookup": {
        "type": "object",
        "required": [
          "exists",
          "supports_lookup",
          "bookmaker_platform_id",
          "bookmaker_user_id",
          "account_holder_name",
          "currency_id",
          "reason"
        ],
        "properties": {
          "exists": {
            "type": "boolean"
          },
          "supports_lookup": {
            "type": "boolean"
          },
          "bookmaker_platform_id": {
            "type": "integer"
          },
          "bookmaker_user_id": {
            "type": "integer"
          },
          "account_holder_name": {
            "type": ["string", "null"]
          },
          "currency_id": {
            "type": ["integer", "null"]
          },
          "reason": {
            "type": ["string", "null"]
          }
        }
      },
      "AccountLookupResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ResponseMetadata"
          },
          {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/AccountLookup"
              }
            }
          }
        ]
      },
      "PaymentRequestBase": {
        "type": "object",
        "required": [
          "bookmaker_platform_id",
          "bookmaker_user_id",
          "amount",
          "operator_code",
          "phone_number",
          "partner_external_id",
          "user_external_id"
        ],
        "properties": {
          "bookmaker_platform_id": {
            "type": "integer",
            "minimum": 1
          },
          "bookmaker_user_id": {
            "type": "integer",
            "minimum": 1
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Montant en XOF."
          },
          "operator_code": {
            "type": "string",
            "maxLength": 64
          },
          "phone_number": {
            "type": "string",
            "pattern": "^(?:\\+226|00226|226)?\\d{8}$",
            "examples": ["+22670000000", "70000000"]
          },
          "partner_external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Votre référence unique d’opération."
          },
          "user_external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Votre référence utilisateur."
          }
        }
      },
      "DepositRequest": {
        "unevaluatedProperties": false,
        "allOf": [
          {
            "$ref": "#/components/schemas/PaymentRequestBase"
          }
        ]
      },
      "WithdrawalRequest": {
        "unevaluatedProperties": false,
        "allOf": [
          {
            "$ref": "#/components/schemas/PaymentRequestBase"
          },
          {
            "type": "object",
            "required": ["withdrawal_code"],
            "properties": {
              "withdrawal_code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "Code de retrait fourni par le bookmaker."
              }
            }
          }
        ]
      },
      "Transaction": {
        "type": "object",
        "required": [
          "id",
          "status",
          "type",
          "amount",
          "partner_external_id",
          "user_external_id",
          "bookmaker_platform_id",
          "phone_number",
          "operator_code",
          "payment_method",
          "gateway",
          "ussd_code",
          "wave_message",
          "created_at",
          "completed_at",
          "failed_at",
          "failure_reason"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "examples": ["PENDING", "INITIATED", "COMPLETED", "FAILED", "CANCELLED"]
          },
          "type": {
            "type": "string",
            "examples": ["DEPOSIT", "PAYOUT"]
          },
          "amount": {
            "type": "number"
          },
          "partner_external_id": {
            "type": ["string", "null"]
          },
          "user_external_id": {
            "type": ["string", "null"]
          },
          "bookmaker_platform_id": {
            "type": ["integer", "null"]
          },
          "phone_number": {
            "type": ["string", "null"]
          },
          "operator_code": {
            "type": ["string", "null"]
          },
          "payment_method": {
            "type": ["string", "null"]
          },
          "gateway": {
            "type": ["string", "null"]
          },
          "ussd_code": {
            "type": ["string", "null"]
          },
          "wave_message": {
            "type": ["string", "null"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failure_reason": {
            "type": ["string", "null"]
          }
        }
      },
      "TransactionResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ResponseMetadata"
          },
          {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Transaction"
              }
            }
          }
        ]
      },
      "TransactionsResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ResponseMetadata"
          },
          {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Transaction"
                }
              }
            }
          }
        ]
      },
      "CommissionPayoutDestination": {
        "type": "object",
        "required": [
          "phone_number",
          "operator_id",
          "operator_name",
          "operator_code"
        ],
        "properties": {
          "phone_number": {
            "type": ["string", "null"]
          },
          "operator_id": {
            "type": ["integer", "null"],
            "minimum": 1
          },
          "operator_name": {
            "type": ["string", "null"]
          },
          "operator_code": {
            "type": ["string", "null"]
          }
        }
      },
      "PartnerCommission": {
        "type": "object",
        "required": [
          "id",
          "period",
          "currency",
          "total_deposits",
          "total_withdrawals",
          "total_volume",
          "commissionable_base",
          "commission_rate",
          "commission_amount",
          "payout_status",
          "is_paid",
          "payment_date",
          "payout_transaction_id",
          "payout_transaction",
          "payout_destination",
          "rate_reviewed",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "period": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "example": "2026-09"
          },
          "currency": {
            "type": "string",
            "const": "XOF"
          },
          "total_deposits": {
            "type": "number",
            "minimum": 0,
            "description": "Volume des dépôts COMPLETED et NORMAL du mois."
          },
          "total_withdrawals": {
            "type": "number",
            "minimum": 0,
            "description": "Volume des retraits COMPLETED et NORMAL du mois."
          },
          "total_volume": {
            "type": "number",
            "minimum": 0
          },
          "commissionable_base": {
            "type": "number",
            "minimum": 0,
            "description": "Part commissionnable des récompenses FastCash, avant application du taux partenaire."
          },
          "commission_rate": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Fraction, par exemple 0.60 pour 60 %."
          },
          "commission_amount": {
            "type": "number",
            "minimum": 0,
            "description": "Montant dû en XOF : commissionable_base × commission_rate."
          },
          "payout_status": {
            "type": "string",
            "enum": ["pending", "processing", "completed", "failed"]
          },
          "is_paid": {
            "type": "boolean"
          },
          "payment_date": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "payout_transaction_id": {
            "type": ["integer", "null"],
            "minimum": 1
          },
          "payout_transaction": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CommissionPayoutTransaction"
              },
              {
                "type": "null"
              }
            ]
          },
          "payout_destination": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CommissionPayoutDestination"
              },
              {
                "type": "null"
              }
            ]
          },
          "rate_reviewed": {
            "type": "boolean",
            "description": "Indique que le taux de ce mois a été ajusté et figé."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommissionPayoutTransaction": {
        "type": "object",
        "required": [
          "id",
          "reference_id",
          "amount",
          "status",
          "failure_reason",
          "created_at",
          "completed_at",
          "failed_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1
          },
          "reference_id": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "description": "Montant réellement envoyé par la transaction de versement."
          },
          "status": {
            "type": ["string", "null"],
            "examples": ["PENDING", "COMPLETED", "FAILED"]
          },
          "failure_reason": {
            "type": ["string", "null"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "PartnerCommissionSummary": {
        "type": "object",
        "required": [
          "commission_count",
          "paid_count",
          "outstanding_count",
          "total_commission_amount",
          "total_paid_amount",
          "total_outstanding_amount"
        ],
        "properties": {
          "commission_count": {
            "type": "integer",
            "minimum": 0
          },
          "paid_count": {
            "type": "integer",
            "minimum": 0
          },
          "outstanding_count": {
            "type": "integer",
            "minimum": 0
          },
          "total_commission_amount": {
            "type": "number",
            "minimum": 0
          },
          "total_paid_amount": {
            "type": "number",
            "minimum": 0
          },
          "total_outstanding_amount": {
            "type": "number",
            "minimum": 0
          }
        }
      },
      "PartnerCommissionsData": {
        "type": "object",
        "required": [
          "partner",
          "currency",
          "current_period",
          "current_period_status",
          "current_commission",
          "summary",
          "commissions"
        ],
        "properties": {
          "partner": {
            "type": "object",
            "required": [
              "id",
              "slug",
              "name",
              "current_commission_rate",
              "payout_destination"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "current_commission_rate": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "payout_destination": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CommissionPayoutDestination"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "currency": {
            "type": "string",
            "const": "XOF"
          },
          "current_period": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
          },
          "current_period_status": {
            "type": "string",
            "enum": ["generated", "no_eligible_volume"]
          },
          "current_commission": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PartnerCommission"
              },
              {
                "type": "null"
              }
            ]
          },
          "summary": {
            "$ref": "#/components/schemas/PartnerCommissionSummary"
          },
          "commissions": {
            "type": "array",
            "description": "Historique complet, du mois le plus récent au plus ancien.",
            "items": {
              "$ref": "#/components/schemas/PartnerCommission"
            }
          }
        }
      },
      "PartnerCommissionsResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ResponseMetadata"
          },
          {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/PartnerCommissionsData"
              }
            }
          }
        ]
      },
      "WebhookTransaction": {
        "type": "object",
        "required": [
          "id",
          "partner_external_id",
          "amount",
          "status",
          "type",
          "bookmaker_platform_id",
          "completed_at",
          "failed_at",
          "failure_reason"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "partner_external_id": {
            "type": ["string", "null"]
          },
          "amount": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "enum": ["COMPLETED", "FAILED", "CANCELLED"]
          },
          "type": {
            "type": "string"
          },
          "bookmaker_platform_id": {
            "type": ["integer", "null"]
          },
          "completed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failed_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "failure_reason": {
            "type": ["string", "null"]
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["event_id", "event_type", "created_at", "transaction"],
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_type": {
            "type": "string",
            "enum": [
              "transaction.completed",
              "transaction.failed",
              "transaction.cancelled"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "transaction": {
            "$ref": "#/components/schemas/WebhookTransaction"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["success", "error"],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "required": ["code", "message", "timestamp", "requestId"],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "VALIDATION_ERROR",
                  "AUTHENTICATION_ERROR",
                  "RATE_LIMIT_EXCEEDED"
                ]
              },
              "message": {
                "type": "string"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              },
              "requestId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      }
    },
    "responses": {
      "ValidationError": {
        "description": "Payload ou paramètre invalide.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Clé API absente ou invalide.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "IP refusée, compte inactif ou accès interdit.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ressource introuvable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Corps de requête supérieur à 64 Kio.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Limite de requêtes partenaire atteinte. Pour une lecture GET, relancez avec backoff exponentiel et jitter. Ne relancez pas automatiquement une création de paiement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "UpstreamError": {
        "description": "Erreur d’un service de paiement ou bookmaker.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unavailable": {
        "description": "Service ou opérateur temporairement indisponible.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "UpstreamTimeout": {
        "description": "Délai du service amont dépassé. Ne relancez jamais automatiquement une création de dépôt ou retrait ; gelez l’opération et escaladez avec requestId et partner_external_id.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InternalError": {
        "description": "Erreur interne.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    }
  }
}
