{
  "openapi": "3.1.0",
  "info": {
    "title": "Cartflox API",
    "version": "2026-09-19",
    "summary": "Payment orchestration for Africa, with your own aggregators.",
    "description": "Cartflox is a payment orchestration API for Africa. Collect Mobile Money and card\npayments through the aggregators you already use (PayDunya, PawaPay, CinetPay,\nFlutterwave, Paystack, FedaPay, Hub2, Stripe and more), send money out to Mobile\nMoney accounts, and receive signed webhooks when a payment or a transfer changes state.\n\nCartflox est une API d'orchestration des paiements pour l'Afrique : encaissez par\nMobile Money et carte avec vos propres passerelles.\n\n## Authentication\n\nEvery server-side call is authenticated with the **secret key** of your workspace,\neither as `Authorization: Bearer <key>` or in the `x-api-key` header.\n\n| Key | Role |\n|---|---|\n| `af_live_pub_...` / `af_test_pub_...` | Public key. Can appear client-side (widget, SoftPay). It can only create a checkout session and read that session's status and payment methods. |\n| `af_live_sec_...` / `af_test_sec_...` | Secret key. Signs your API calls and your webhooks. Server side only. |\n\nPartner endpoints (`/v1/partner/*`) use a separate shared secret in the `x-partner-secret` header.\n\n## Test mode and `livemode`\n\nEvery workspace has two sets of keys: production (`af_live_*`) and test (`af_test_*`).\nSame API, same routes. A session created with a test key is a **test transaction**: no\naggregator is called, the hosted payment page shows a \"Test mode\" banner with two\nbuttons (simulate a success / a failure), and webhooks are sent normally.\n\nTest data is isolated: it never appears in production lists, statistics or exports, and\nit is purged after 90 days. Every object response (session, status, transfer, webhook\ndelivery) and every webhook body carries `\"livemode\": true|false`.\n\nA distinct test `webhookUrl` can be set with `PATCH /v1/config/webhook` and `{\"mode\": \"test\"}`;\notherwise the production URL receives test events with `livemode: false`.\n\nA workspace whose identity is not verified yet is entirely in test mode: even its\nproduction keys create test sessions (`livemode: false`) until Cartflox switches it live.\nTransfers are refused with a test key (`403`, code `test_mode`) and payment links live in\nproduction only. For automated tests, `POST /checkout/sandbox` settles a test session\n(success or failure) without opening the payment page.\n\n## Idempotency\n\n`POST /v1/checkout/sessions` and `POST /v1/transfers` accept an optional `Idempotency-Key`\nheader (a unique string per order or per transfer). When the same call is replayed, the\nexisting object is returned with HTTP `200` and the header `Idempotent-Replayed: true`\ninstead of creating a second one. For transfers this is strongly recommended: it is what\nprevents sending the money twice.\n\n## Rate limits\n\nLimits apply per minute. Beyond them the response is `429`; `POST /v1/checkout/sessions`\nalso sets a `Retry-After` header (seconds).\n\n| Endpoint | Limit |\n|---|---|\n| `POST /v1/checkout/sessions` | 60 per minute per IP |\n| `POST /v1/payment-links` | 60 per minute per IP |\n| `POST /v1/route` | 120 per minute per IP |\n| `POST /v1/transfers` | 30 per minute per workspace, plus a daily cap (200 transfers per day by default, error code `plafond_journalier`) |\n| `POST /v1/webhooks/deliveries/{id}/resend` | 30 per minute per workspace |\n| `POST /checkout/sandbox` | 30 per minute per IP |\n\nNeed more for a peak (launch, ticketing)? Contact Cartflox before the event.\n\n## Webhooks\n\nCartflox sends a `POST` request to your webhook URL on every status change of a payment\nor a transfer. The body is `{ \"event\", \"livemode\", \"data\", \"timestamp\" }`. Events:\n`payment.completed`, `payment.failed`, `payment.cancelled`, `payment.updated`,\n`transfer.succeeded`, `transfer.failed`. You choose the events you receive with\n`PATCH /v1/config/webhook` and `{\"events\": [...]}`; by default all of them are sent.\n\n**Delivery and retries.** Every send is logged as a `webhook.delivery` object. Your\nendpoint has 10 seconds to answer; any `2xx` counts as an acknowledgement. Failed\ndeliveries are retried automatically: 10 attempts over 72 hours (immediately, then\n+1 min, +5 min, +15 min, +1 h, +3 h, +6 h, +12 h, +24 h, +24 h). Deliveries can be\nlisted and resent with the `/v1/webhooks/deliveries` endpoints.\n\n**Headers.** Every request also carries `X-Afriflow-Event` (the event name) and\n`X-Afriflow-Delivery` (the delivery id, identical from one attempt to the next: use it to\ndeduplicate), with `User-Agent: Cartflox-Webhooks/2`.\n\n**Signature.** Each request carries `X-Afriflow-Timestamp` (Unix seconds) and\n`X-Afriflow-Signature: t=<ts>,v1=<hex>` where `v1` is the HMAC-SHA256, with your secret\nkey (production key, or test key for a test event), of the string `<ts>.<raw body>`.\nCompare in constant time and reject the request when `|now - ts|` exceeds 5 minutes.\n\n## Accepted currencies\n\nXOF, XAF, GHS, NGN, KES, TZS, UGX, RWF, ZMW, MWK, CDF, ETB, MZN, ZAR, EGP, MAD, GNF, SLE,\nLRD, GMD, MGA, MUR, USD, EUR, GBP. Currencies without minor units (amounts are whole\nnumbers): XOF, XAF, GNF, UGX, RWF, CDF, MGA, KMF, DJF, BIF.\n\n## Errors\n\nErrors are reported by the HTTP status code and a JSON body `{ \"error\": \"<message>\" }`.\nTransfer endpoints add a machine-readable `code`.\n\n| Status | Meaning |\n|---|---|\n| `400` | Missing or invalid parameter (`amount` missing, `webhookUrl` not public, incomplete link...) |\n| `401` | API key missing or invalid: `Missing API key` or `Invalid API Key` |\n| `402` | Insufficient balance (transfers from a Connect balance) |\n| `403` | Forbidden: transfers closed on this workspace, Connect workspace, unverified identity, or gateway not owned by this application |\n| `404` | Session, link, transfer, delivery or gateway not found |\n| `409` | The session can no longer be paid (already cancelled or refunded) |\n| `429` | Too many requests: honour the `Retry-After` header (seconds) |\n| `500` | Internal error: retry, then contact Cartflox if it persists |\n| `502` | The aggregator could not initiate the payment: the message contains its answer |\n| `503` | Partner API disabled (shared secret not configured) |\n",
    "contact": {
      "name": "Cartflox support",
      "url": "https://cartflox.com/contact",
      "email": "support@cartflox.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://cartflox.com"
    }
  },
  "externalDocs": {
    "description": "Cartflox documentation (French)",
    "url": "https://cartflox.com/docs/"
  },
  "servers": [
    {
      "url": "https://cartflox.com/api",
      "description": "Production and test mode (the key decides)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Checkout",
      "description": "Hosted payment sessions, their status and their available payment methods."
    },
    {
      "name": "Payment links",
      "description": "Fixed-amount hosted pages to share by WhatsApp, e-mail or social networks."
    },
    {
      "name": "Transfers",
      "description": "Outgoing money transfers to Mobile Money accounts."
    },
    {
      "name": "Webhooks",
      "description": "Delivery log and resend of outgoing webhooks."
    },
    {
      "name": "Configuration",
      "description": "Webhook URL and event selection of the workspace."
    },
    {
      "name": "Routing",
      "description": "Smart routing between the aggregators of a workspace."
    },
    {
      "name": "Transactions",
      "description": "Transaction export (dashboard session)."
    },
    {
      "name": "Partner",
      "description": "Provisioning of merchants by partner products (shared secret)."
    }
  ],
  "paths": {
    "/v1/checkout/sessions": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "operationId": "createCheckoutSession",
        "summary": "Create a checkout session",
        "description": "A session is a payment intent. Create it server-side, then redirect your customer to\n`url`, the hosted payment page, which handles the operator choice, the Wave\nredirection, Orange Money OTP codes and card payments.\n\nAccepts the secret key, or the public key (widget) which can do nothing else.\nA session created with a test key is a test transaction (`livemode: false`); so is any\nsession of a workspace not switched live yet (identity not verified), whatever the key.\n\nNever fulfil an order on the sole basis of the redirection to `success_url`: wait\nfor the `payment.completed` webhook or check the status server-side.\n",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckoutSessionRequest"
              },
              "examples": {
                "order": {
                  "summary": "A 5 000 XOF order",
                  "value": {
                    "amount": 5000,
                    "currency": "XOF",
                    "customer_name": "Awa Koné",
                    "customer_email": "awa@example.com",
                    "customer_phone": "+2250700000000",
                    "description": "Commande #1042",
                    "success_url": "https://maboutique.com/merci",
                    "cancel_url": "https://maboutique.com/panier",
                    "metadata": {
                      "order_id": "1042"
                    },
                    "merchant_name": "Ma Boutique",
                    "merchant_logo": "https://maboutique.com/logo.png"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                },
                "examples": {
                  "created": {
                    "$ref": "#/components/examples/CheckoutSessionExample"
                  }
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay, the existing session is returned unchanged.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "400": {
            "description": "`amount` is missing or not greater than zero.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "amount is required and must be > 0"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "More than 60 sessions per minute from this IP.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many requests. Please retry later."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/checkout/sessions/{id}/status": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "operationId": "getCheckoutSessionStatus",
        "summary": "Get the status of a session",
        "description": "Current state of a session, for polling: at the customer's return on `success_url`,\nor to catch up on a missed webhook. While the session is `PENDING`, Cartflox also\nchecks the payment directly with the aggregator (at most once every 8 seconds per\nsession). Accepts the secret key or the public key.\n\nOnly this status and the webhook are authoritative: the redirection to `success_url`\ncan happen before the operator confirms.\n",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionStatus"
                },
                "examples": {
                  "paid": {
                    "$ref": "#/components/examples/SessionStatusExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Session not found, or not owned by this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Session introuvable"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/checkout/sessions/{id}/methods": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "operationId": "listCheckoutSessionMethods",
        "summary": "List the payment methods of a session",
        "description": "The operators available for a session, optionally filtered by country, for merchants\nwho build their own operator picker. Card methods (UEMOA, international) are returned\nwhatever the country asked. Operator codes depend on the gateway serving them: do not\nhard-code them, read them from the response. Accepts the secret key or the public key.\n",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "ISO 3166-1 alpha-2 code (`ci`, `sn`, `bj`, `ml`, `bf`, `tg`, `cm`, `gh`, `ng`, `ke`...) or country name. Without it, every active operator is returned.",
            "schema": {
              "type": "string",
              "examples": [
                "ci"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Available methods.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionMethods"
                },
                "example": {
                  "id": "cmf3k2p1x0001abcd9e8f7g6h",
                  "currency": "XOF",
                  "methods": [
                    {
                      "code": "ORANGE_CIV",
                      "gatewayId": "cm9xabc123",
                      "name": "Orange Money CI",
                      "provider": "PawaPay",
                      "country": "Côte d'Ivoire",
                      "type": "MOBILE_MONEY",
                      "flag": "CI",
                      "logo": "/icons/methods/orange_money.svg"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Session not found, or not owned by this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Session introuvable"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/checkout/sandbox": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "operationId": "settleSandboxSession",
        "summary": "Settle a test session without opening the payment page",
        "description": "Simulated outcome of a **test** payment, for automated tests. Marks the session\n`SUCCESS` or `FAILED` with `provider: \"sandbox\"` and sends the `payment.completed` or\n`payment.failed` webhook exactly as in production, with `livemode: false`. No API key\nis needed: the session identifier, impossible to guess, is enough.\n\nRefused for a production session of a live workspace (`403`) and for a session already\nsettled (`409`). Note the path: `/checkout/sandbox`, not under `/v1`. Errors use the\npayment page shape `{ \"success\": false, \"message\": \"...\" }`.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxSettleRequest"
              },
              "example": {
                "transactionId": "cmu8a1b2c0001xyz",
                "issue": "succes"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Session settled, webhook sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxSettleResponse"
                },
                "example": {
                  "success": true,
                  "sandbox": true,
                  "status": "SUCCESS",
                  "message": "Paiement marqué réussi en mode test. Aucun argent n'a été encaissé."
                }
              }
            }
          },
          "400": {
            "description": "`transactionId` missing, or `issue` not `succes` / `echec`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxError"
                },
                "example": {
                  "success": false,
                  "message": "transactionId manquant"
                }
              }
            }
          },
          "403": {
            "description": "The workspace collects real money and this session is not a test session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxError"
                }
              }
            }
          },
          "404": {
            "description": "Session not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxError"
                },
                "example": {
                  "success": false,
                  "message": "Transaction introuvable"
                }
              }
            }
          },
          "409": {
            "description": "The session is no longer payable (already `SUCCESS`, `REFUNDED` or `CANCELLED`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxError"
                }
              }
            }
          },
          "429": {
            "description": "More than 30 calls per minute from this IP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payment-links": {
      "post": {
        "tags": [
          "Payment links"
        ],
        "operationId": "createPaymentLink",
        "summary": "Create a payment link",
        "description": "A payment link is a hosted page with a fixed amount, to share by WhatsApp, e-mail or\nsocial networks. The customer's phone number is always requested on the page, for\nMobile Money. The link is created `active` and never expires by itself.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentLinkRequest"
              },
              "example": {
                "title": "Formation Odoo",
                "amount": 25000,
                "currency": "XOF"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link created (the API answers `200`, not `201`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                },
                "example": {
                  "success": true,
                  "id": "cmf3lz0001abcd",
                  "url": "https://cartflox.com/pay/k3j9x2ab",
                  "slug": "k3j9x2ab",
                  "amount": 25000,
                  "currency": "XOF"
                }
              }
            }
          },
          "400": {
            "description": "`title` or `amount` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Missing required fields"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Test key: payment links live in production only (`code: test_mode`). Create a test checkout session instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Payment links are not available in test mode: create a checkout session with your test key instead.",
                  "code": "test_mode"
                }
              }
            }
          },
          "429": {
            "description": "More than 60 links per minute from this IP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many requests"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/payment-links/{id}/check": {
      "get": {
        "tags": [
          "Payment links"
        ],
        "operationId": "checkPaymentLink",
        "summary": "Check whether a payment link has been paid",
        "description": "Returns the most recent successful payment of the link, if any. Until a payment\nsucceeds, the answer is `{ \"success\": true, \"paid\": false, \"status\": \"WAITING\" }`,\nor the status of the latest non-failed attempt.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifier of the payment link (`id` returned at creation, not the slug).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment state of the link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLinkCheck"
                },
                "examples": {
                  "paid": {
                    "summary": "Paid",
                    "value": {
                      "success": true,
                      "paid": true,
                      "status": "SUCCESS",
                      "transaction": {
                        "id": "cmf3m1abcd",
                        "amount": 25000,
                        "customerName": "Awa Koné",
                        "customerEmail": "awa@example.com"
                      }
                    }
                  },
                  "waiting": {
                    "summary": "Not paid yet",
                    "value": {
                      "success": true,
                      "paid": false,
                      "status": "WAITING"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/route": {
      "post": {
        "tags": [
          "Routing"
        ],
        "operationId": "routePayment",
        "summary": "Routing decision for a payment",
        "description": "Returns the best aggregator for a given context (country, amount, currency, operator),\nwith ordered fallbacks and a confidence score. When `paymentMethod` is absent and\n`phone` is present, the operator is inferred from the number prefix. Requires a secret key.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoutingRequest"
              },
              "example": {
                "country": "SN",
                "currency": "XOF",
                "amount": 5000,
                "phone": "+221701234567",
                "availableProviders": [
                  "paydunya",
                  "cinetpay"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Routing decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoutingResponse"
                },
                "example": {
                  "routing": {
                    "provider": "paydunya",
                    "reason": "paydunya sélectionné pour méthode orange-money, pays SN, devise XOF (priorité 9, taux de succès estimé 95%)",
                    "alternates": [
                      "cinetpay"
                    ],
                    "confidence": 0.85
                  },
                  "detectedOperator": "orange-money",
                  "availableMethods": [
                    {
                      "method": "orange-money",
                      "providers": [
                        "paydunya",
                        "cinetpay"
                      ]
                    },
                    {
                      "method": "wave",
                      "providers": [
                        "paydunya",
                        "cinetpay"
                      ]
                    }
                  ],
                  "rankedProviders": [
                    {
                      "provider": "paydunya",
                      "score": 11.05
                    },
                    {
                      "provider": "cinetpay",
                      "score": 8.36
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`country`, `amount` (number) or `availableProviders` (non-empty array) is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "country is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "More than 120 calls per minute from this IP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many requests"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "get": {
        "tags": [
          "Routing"
        ],
        "operationId": "getRoutingOptions",
        "summary": "Available methods and ranked providers for a country",
        "description": "Quick check, without a decision: the payment methods available in a country for the\ngiven providers, and the providers ranked by estimated reliability. Without\n`providers`, every provider known to Cartflox is considered. This endpoint does not\nrequire an API key.\n",
        "security": [],
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": true,
            "description": "ISO 3166-1 alpha-2 country code.",
            "schema": {
              "type": "string",
              "examples": [
                "SN"
              ]
            }
          },
          {
            "name": "providers",
            "in": "query",
            "required": false,
            "description": "Comma-separated provider keys (`paydunya,cinetpay`).",
            "schema": {
              "type": "string",
              "examples": [
                "paydunya,cinetpay"
              ]
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "ISO 4217 currency code.",
            "schema": {
              "type": "string",
              "examples": [
                "XOF"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Methods and ranking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoutingOptions"
                },
                "example": {
                  "country": "SN",
                  "currency": "XOF",
                  "availableMethods": [
                    {
                      "method": "wave",
                      "providers": [
                        "paydunya",
                        "cinetpay"
                      ]
                    }
                  ],
                  "rankedProviders": [
                    {
                      "provider": "paydunya",
                      "score": 11.05
                    },
                    {
                      "provider": "cinetpay",
                      "score": 8.36
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`country` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "country query param is required"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/transactions/export": {
      "get": {
        "tags": [
          "Transactions"
        ],
        "operationId": "exportTransactions",
        "summary": "Export the transactions of the selected workspace",
        "description": "CSV (default) or JSON export of up to 10 000 transactions, newest first. This endpoint\nis used by the dashboard: it is authenticated by the dashboard session cookie, not by\nan API key (an API key gets `401 Unauthorized`). Test transactions never appear in\nproduction exports.\n",
        "security": [
          {
            "dashboardSession": []
          }
        ],
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "json"
              ],
              "default": "csv"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter on a status; `ALL` or absent means every status.",
            "schema": {
              "type": "string",
              "enum": [
                "ALL",
                "PENDING",
                "SUCCESS",
                "FAILED",
                "CANCELLED",
                "REFUNDED"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start date (inclusive), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End date (inclusive, until 23:59:59.999), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive search on `orderId`, `customerName` and `customerEmail`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "Columns: ID, Order ID, Status, Amount, Currency, Provider, Payment Type, Customer Name, Customer Email, Customer Phone, Provider Ref, Created At, Completed At."
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionExport"
                }
              }
            }
          },
          "400": {
            "description": "No workspace selected in the dashboard session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No application selected"
                }
              }
            }
          },
          "401": {
            "description": "No dashboard session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/transfers": {
      "post": {
        "tags": [
          "Transfers"
        ],
        "operationId": "createTransfer",
        "summary": "Send money to a Mobile Money account",
        "description": "Sends an amount to the Mobile Money account of a supplier, an employee or a customer to\nrefund. Cartflox hands the transfer to one of your gateways that can send money\n(PawaPay, PayDunya, Hub2, Notch Pay, Monetbil, Flutterwave, Paystack, FedaPay,\nFeexPay), with your own keys. The money leaves your aggregator's wallet, with the\naggregator's own fees: `fee` is `0`.\n\nRequires the **secret** key: the public key is refused. Connect workspaces (money held\nby Cartflox) do not have access to transfers for now (`403 connect_non_disponible`).\n\nSend an `Idempotency-Key` header (or the `idempotency_key` field): a replayed call\nreturns the existing transfer with HTTP `200` and `Idempotent-Replayed: true` instead\nof sending the money a second time.\n\nThe status right after the call is most often `processing`. Follow it with\n`GET /v1/transfers/{id}` and the `transfer.succeeded` / `transfer.failed` webhooks.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTransferRequest"
              },
              "example": {
                "amount": 50000,
                "country": "CI",
                "operator": "orange_money",
                "phone": "0712345678",
                "recipient_name": "Awa Koné",
                "description": "Salaire septembre"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Transfer created and handed to the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "processing": {
                    "$ref": "#/components/examples/TransferExample"
                  }
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay, the existing transfer is returned.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request. `code` is one of `invalid_body`, `pays_non_desservi`,\n`operateur_manquant`, `operateur_non_desservi`, `methode_desactivee`,\n`telephone_invalide`, `devise_invalide`, `montant_invalide`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Numéro invalide pour Côte d'Ivoire.",
                  "code": "telephone_invalide"
                }
              }
            }
          },
          "401": {
            "description": "Key missing, invalid, or public key (`code: unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Clé API invalide. Les transferts exigent la clé SECRÈTE de l'espace.",
                  "code": "unauthorized"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient Connect balance (`code: solde_insuffisant`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`test_mode` (test key: transfers move real money), `transferts_inactifs` (closed by Cartflox administration), `connect_non_disponible` (Connect workspace) or `identite_non_verifiee`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Les transferts d'argent ne sont pas disponibles sur un espace Connect.",
                  "code": "connect_non_disponible"
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found (`code: espace_introuvable`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited` (more than 30 transfers per minute) or `plafond_journalier` (daily cap of the workspace reached, resets at midnight GMT).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Trop de demandes : 30 transferts par minute au plus.",
                  "code": "rate_limited"
                }
              }
            }
          },
          "500": {
            "description": "Internal error, the transfer was not created (`code: internal`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Transfers"
        ],
        "operationId": "listTransfers",
        "summary": "List transfers, or the countries and operators open to the workspace",
        "description": "Without `options`, the latest transfers of the workspace, newest first, filterable by\nstatus. With `options=1`, the countries, currencies and operators the workspace can\nsend to, given its gateways (the shape of the response is then `TransferOptions`).\n",
        "parameters": [
          {
            "name": "options",
            "in": "query",
            "required": false,
            "description": "Set to `1` to get the countries and operators instead of the list.",
            "schema": {
              "type": "string",
              "examples": [
                "1"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/TransferStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Number of transfers (1 to 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The list, or the options.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/TransferList"
                    },
                    {
                      "$ref": "#/components/schemas/TransferOptions"
                    }
                  ]
                },
                "examples": {
                  "list": {
                    "summary": "Transfer list",
                    "value": {
                      "data": [
                        {
                          "id": "cmfx1a2b3c4d5e6f7g8h9i0j",
                          "reference": "ddf9f0bf-f310-4126-83d9-dc8bc92b6e79",
                          "status": "succeeded",
                          "amount": 50000,
                          "currency": "XOF",
                          "fee": 0,
                          "country": "CI",
                          "operator": "orange_money",
                          "provider_operator": "ORANGE_CIV",
                          "phone": "2250712345678",
                          "recipient_name": "Awa Koné",
                          "description": "Salaire septembre",
                          "provider": "PawaPay",
                          "provider_reference": null,
                          "failure": null,
                          "idempotency_key": "salaire-2026-09-awa",
                          "livemode": true,
                          "created": "2026-09-18T09:12:41.000Z",
                          "completed": "2026-09-18T09:13:05.000Z"
                        }
                      ],
                      "count": 1
                    }
                  },
                  "options": {
                    "summary": "Options (`?options=1`)",
                    "value": {
                      "countries": [
                        {
                          "code": "CI",
                          "name": "Côte d'Ivoire",
                          "currency": "XOF",
                          "operators": [
                            {
                              "code": "orange_money",
                              "name": "Orange Money",
                              "provider": "PawaPay"
                            },
                            {
                              "code": "wave",
                              "name": "Wave",
                              "provider": "PawaPay"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Key missing, invalid, or public key (`code: unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/transfers/{id}": {
      "get": {
        "tags": [
          "Transfers"
        ],
        "operationId": "getTransfer",
        "summary": "Get a transfer",
        "description": "By Cartflox identifier (`id`) or by `reference`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Transfer `id` or `reference`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transfer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "transfer": {
                    "$ref": "#/components/examples/TransferExample"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Key missing, invalid, or public key (`code: unauthorized`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in this workspace (`code: not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Transfert introuvable.",
                  "code": "not_found"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook deliveries",
        "description": "The delivery log of the workspace, newest first (50 by default, 200 at most). A test key\nsees only test deliveries, a production key only production ones. `status=pending` also\ncovers deliveries being sent right now.\n",
        "parameters": [
          {
            "name": "transaction_id",
            "in": "query",
            "required": false,
            "description": "Only the deliveries about this payment (session id).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/WebhookDeliveryStatus"
            }
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Only the deliveries of this event.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventName"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": "cmu8d4e5f0003abc",
                      "object": "webhook.delivery",
                      "event": "payment.completed",
                      "url": "https://maboutique.com/webhooks/cartflox",
                      "status": "delivered",
                      "attempts": 2,
                      "max_attempts": 10,
                      "next_attempt_at": null,
                      "last_status_code": 200,
                      "last_error": null,
                      "response_ms": 412,
                      "transaction_id": "cmf3k2p1x0001abcd9e8f7g6h",
                      "transfer_id": null,
                      "livemode": true,
                      "created_at": "2026-09-19T08:00:00.000Z",
                      "delivered_at": "2026-09-19T08:01:03.000Z"
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "400": {
            "description": "`status` is not `pending`, `delivered` or `failed`, or `event` is unknown (the body then lists `available_events`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "status must be pending, delivered or failed"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/webhooks/deliveries/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookDelivery",
        "summary": "Get a webhook delivery",
        "parameters": [
          {
            "$ref": "#/components/parameters/DeliveryId"
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                },
                "examples": {
                  "delivered": {
                    "$ref": "#/components/examples/WebhookDeliveryExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/deliveries/{id}/resend": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "resendWebhookDelivery",
        "summary": "Resend a webhook delivery",
        "description": "Sends the delivery again right away, whatever its state (delivered, failed or pending),\nwith a fresh signature, to the current URL of the workspace, and returns it updated.\n`404` when it does not belong to the workspace (or to the other mode).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/DeliveryId"
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery after the new attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                },
                "examples": {
                  "resent": {
                    "$ref": "#/components/examples/WebhookDeliveryExample"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "description": "More than 30 resends per minute for this workspace (`code: rate_limited`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many resends: 30 per minute at most.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/v1/config/webhook": {
      "get": {
        "tags": [
          "Configuration"
        ],
        "operationId": "getWebhookConfig",
        "summary": "Get the webhook configuration",
        "description": "Production URL, test URL and the events the workspace receives (`events` lists every event when none was selected).",
        "responses": {
          "200": {
            "description": "Current configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                },
                "example": {
                  "success": true,
                  "webhookUrl": "https://maboutique.com/webhooks/cartflox",
                  "testWebhookUrl": null,
                  "events": [
                    "payment.completed",
                    "payment.failed",
                    "payment.cancelled",
                    "payment.updated",
                    "transfer.succeeded",
                    "transfer.failed"
                  ],
                  "available_events": [
                    "payment.completed",
                    "payment.failed",
                    "payment.cancelled",
                    "payment.updated",
                    "transfer.succeeded",
                    "transfer.failed"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "tags": [
          "Configuration"
        ],
        "operationId": "updateWebhookConfig",
        "summary": "Set the webhook URL, the test URL or the events",
        "description": "Pass `webhookUrl`, `events`, or both. `webhookUrl` must be a public `https` address:\nlocal addresses, private networks and cloud metadata hosts are refused; an empty string\nremoves the webhook. `mode` says which address is set: `live` or `test`; when absent,\nthe address of the key's own mode (a test key sets the test address). Without a test\naddress, the production address receives test events with `livemode: false`.\n`events` selects the events you receive; the full list (or an empty one) means all of\nthem, future events included.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookConfigRequest"
              },
              "examples": {
                "production": {
                  "summary": "Production URL",
                  "value": {
                    "webhookUrl": "https://maboutique.com/webhooks/cartflox"
                  }
                },
                "test": {
                  "summary": "Test URL",
                  "value": {
                    "webhookUrl": "https://staging.maboutique.com/webhooks/cartflox",
                    "mode": "test"
                  }
                },
                "events": {
                  "summary": "Event selection",
                  "value": {
                    "webhookUrl": "https://maboutique.com/webhooks/cartflox",
                    "events": [
                      "payment.completed",
                      "payment.failed",
                      "payment.cancelled"
                    ]
                  }
                },
                "eventsOnly": {
                  "summary": "Events only, address unchanged",
                  "value": {
                    "events": [
                      "payment.completed",
                      "payment.failed",
                      "payment.cancelled"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Configuration saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                },
                "example": {
                  "success": true,
                  "webhookUrl": "https://maboutique.com/webhooks/cartflox",
                  "testWebhookUrl": null,
                  "events": [
                    "payment.completed",
                    "payment.failed",
                    "payment.cancelled"
                  ],
                  "available_events": [
                    "payment.completed",
                    "payment.failed",
                    "payment.cancelled",
                    "payment.updated",
                    "transfer.succeeded",
                    "transfer.failed"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No JSON body, nothing to update (neither `webhookUrl` nor `events`), `webhookUrl` not a string or not a public https URL, `events` not an array, or unknown event names (the body then lists `available_events`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "notPublic": {
                    "value": {
                      "error": "webhookUrl must be a public https URL"
                    }
                  },
                  "unknownEvent": {
                    "value": {
                      "error": "Unknown events: payment.refunded",
                      "available_events": [
                        "payment.completed",
                        "payment.failed",
                        "payment.cancelled",
                        "payment.updated",
                        "transfer.succeeded",
                        "transfer.failed"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/v1/partner/merchants": {
      "post": {
        "tags": [
          "Partner"
        ],
        "operationId": "createPartnerMerchant",
        "summary": "Provision a merchant for a partner product",
        "description": "Reserved to Cartflox partner products (LinkAfr / Ma Boutique). Creates the user if\nabsent, the workspace (`own_keys` payment mode) and its API keys. Idempotent by\n(`email`, `shop_name`): calling again returns the existing merchant, and updates the\nwebhook URL when a different one is given.\n",
        "security": [
          {
            "partnerSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePartnerMerchantRequest"
              },
              "example": {
                "email": "vendeur@example.com",
                "name": "Awa Koné",
                "shop_name": "Boutique Awa",
                "whatsapp": "+2250700000000",
                "webhook_url": "https://linkafr.com/webhooks/cartflox"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merchant and its keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerMerchant"
                },
                "example": {
                  "application_id": "cmkki1url0000vxi8j8bjjgnl",
                  "public_key": "af_live_pub_0123456789abcdef0123456789abcdef0123",
                  "secret_key": "af_live_sec_0123456789abcdef0123456789abcdef0123456789abcdef",
                  "payment_mode": "own_keys",
                  "commission_bps": 0
                }
              }
            }
          },
          "400": {
            "description": "Invalid `email` or `shop_name` shorter than 2 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "shop_name requis"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/PartnerUnauthorized"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/PartnerDisabled"
          }
        }
      }
    },
    "/v1/partner/merchants/{id}/balance": {
      "get": {
        "tags": [
          "Partner"
        ],
        "operationId": "getPartnerMerchantBalance",
        "summary": "Balance of a partner merchant",
        "security": [
          {
            "partnerSecret": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          }
        ],
        "responses": {
          "200": {
            "description": "Ledger balance, pending payouts and available amount, in XOF.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerBalance"
                },
                "example": {
                  "balance": 125000,
                  "pending_payouts": 25000,
                  "available": 100000,
                  "currency": "XOF"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/PartnerUnauthorized"
          },
          "404": {
            "$ref": "#/components/responses/MerchantNotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/PartnerDisabled"
          }
        }
      }
    },
    "/v1/partner/merchants/{id}/payouts": {
      "get": {
        "tags": [
          "Partner"
        ],
        "operationId": "listPartnerMerchantPayouts",
        "summary": "Payout history of a partner merchant",
        "description": "The 20 most recent payout requests, newest first.",
        "security": [
          {
            "partnerSecret": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          }
        ],
        "responses": {
          "200": {
            "description": "Payouts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPayoutList"
                },
                "example": {
                  "payouts": [
                    {
                      "id": "cmpay0001abcd",
                      "amount": 25000,
                      "currency": "XOF",
                      "status": "PENDING",
                      "method": "wave",
                      "recipient": "+2250700000000",
                      "requestedAt": "2026-09-18T09:12:41.000Z",
                      "processedAt": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/PartnerUnauthorized"
          },
          "404": {
            "$ref": "#/components/responses/MerchantNotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/PartnerDisabled"
          }
        }
      },
      "post": {
        "tags": [
          "Partner"
        ],
        "operationId": "createPartnerMerchantPayout",
        "summary": "Request a payout for a partner merchant",
        "description": "Same rules as the dashboard: minimum 1 000 F, `available = balance - pending payouts`.\nThe amount is truncated to a whole number. Cartflox administration is notified and\nprocesses the payout by hand.\n",
        "security": [
          {
            "partnerSecret": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePartnerPayoutRequest"
              },
              "example": {
                "amount": 25000,
                "method": "wave",
                "recipient": "+2250700000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payout requested.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPayoutCreated"
                },
                "example": {
                  "id": "cmpay0001abcd",
                  "status": "PENDING",
                  "amount": 25000,
                  "method": "wave",
                  "recipient": "+2250700000000",
                  "requested_at": "2026-09-18T09:12:41.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid method, invalid recipient number (fewer than 8 digits), amount below 1 000 F, or insufficient available balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Montant minimum : 1000 F"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/PartnerUnauthorized"
          },
          "404": {
            "$ref": "#/components/responses/MerchantNotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/PartnerDisabled"
          }
        }
      }
    }
  },
  "webhooks": {
    "payment.completed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookPaymentCompleted",
        "summary": "A payment was confirmed (status SUCCESS)",
        "description": "The event to wait for before fulfilling an order. Handle it idempotently, keyed on `data.id` and `data.status`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentEvent"
              },
              "example": {
                "event": "payment.completed",
                "livemode": true,
                "data": {
                  "id": "cmf3k2p1x0001abcd9e8f7g6h",
                  "order_id": "CS-MF3K2A-9X1QZ",
                  "status": "SUCCESS",
                  "amount": 5000,
                  "currency": "XOF",
                  "provider": "PayDunya",
                  "provider_reference": "pd_7f3a9c",
                  "customer_name": "Awa Koné",
                  "customer_email": "awa@example.com",
                  "customer_phone": "+2250700000000",
                  "metadata": {
                    "order_id": "1042",
                    "source": "checkout_session"
                  },
                  "completed_at": "2026-09-02T10:17:42.000Z"
                },
                "timestamp": "2026-09-02T10:17:42.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery. Answer within 10 seconds and process afterwards."
          }
        }
      }
    },
    "payment.failed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookPaymentFailed",
        "summary": "A payment failed or was refused (status FAILED)",
        "description": "The customer can retry on the same session.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    },
    "payment.cancelled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookPaymentCancelled",
        "summary": "A payment was cancelled (status CANCELLED)",
        "description": "Abandoned by the customer, or cancelled by the merchant. The session can no longer be paid.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    },
    "payment.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookPaymentUpdated",
        "summary": "A payment was refunded, or went back to PENDING",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    },
    "transfer.succeeded": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookTransferSucceeded",
        "summary": "An outgoing transfer reached the recipient",
        "description": "`data` is the full transfer object.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    },
    "transfer.failed": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookTransferFailed",
        "summary": "An outgoing transfer was refused by the operator or the provider",
        "description": "`data.failure` carries the code and the message. Nothing was debited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SignatureTimestampHeader"
          },
          {
            "$ref": "#/components/parameters/SignatureHeader"
          },
          {
            "$ref": "#/components/parameters/EventHeader"
          },
          {
            "$ref": "#/components/parameters/DeliveryHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` acknowledges the delivery."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer <secret key>` (`af_live_sec_...` or `af_test_sec_...`). The public key (`af_live_pub_...` / `af_test_pub_...`) is accepted only to create a session and read its status and methods."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Alternative to the bearer token, same key."
      },
      "partnerSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-partner-secret",
        "description": "Shared secret of a Cartflox partner product. Partner endpoints only."
      },
      "dashboardSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "cartflox.session_token",
        "description": "Browser session of the Cartflox dashboard. Used by the transaction export only."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A unique string per order or per transfer. A replayed call returns the existing object with HTTP `200` and `Idempotent-Replayed: true`. Kept at 120 characters at most for transfers.",
        "schema": {
          "type": "string",
          "maxLength": 120,
          "examples": [
            "commande-1042"
          ]
        }
      },
      "SessionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Session identifier returned at creation.",
        "schema": {
          "type": "string",
          "examples": [
            "cmf3k2p1x0001abcd9e8f7g6h"
          ]
        }
      },
      "DeliveryId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Webhook delivery identifier.",
        "schema": {
          "type": "string"
        }
      },
      "MerchantId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "`application_id` returned when the merchant was provisioned.",
        "schema": {
          "type": "string"
        }
      },
      "SignatureTimestampHeader": {
        "name": "X-Afriflow-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix timestamp (seconds) of the send.",
        "schema": {
          "type": "string",
          "examples": [
            "1756808262"
          ]
        }
      },
      "SignatureHeader": {
        "name": "X-Afriflow-Signature",
        "in": "header",
        "required": true,
        "description": "`t=<timestamp>,v1=<HMAC-SHA256 hex of \"<timestamp>.<raw body>\" with your secret key>`.",
        "schema": {
          "type": "string",
          "examples": [
            "t=1756808262,v1=5f1c0b2f4c6a8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70"
          ]
        }
      },
      "EventHeader": {
        "name": "X-Afriflow-Event",
        "in": "header",
        "required": true,
        "description": "Name of the event.",
        "schema": {
          "$ref": "#/components/schemas/WebhookEventName"
        }
      },
      "DeliveryHeader": {
        "name": "X-Afriflow-Delivery",
        "in": "header",
        "required": true,
        "description": "Identifier of the delivery, identical from one attempt to the next. Use it to deduplicate.",
        "schema": {
          "type": "string",
          "examples": [
            "cmu8d4e5f0003abc"
          ]
        }
      }
    },
    "headers": {
      "IdempotentReplayed": {
        "description": "Present, with the value `true`, when the object was returned from a previous call with the same `Idempotency-Key`.",
        "schema": {
          "type": "string",
          "const": "true"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "API key missing or invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "summary": "Missing key",
                "value": {
                  "error": "Missing API key. Use Authorization: Bearer <key> or x-api-key header."
                }
              },
              "invalid": {
                "summary": "Invalid key",
                "value": {
                  "error": "Invalid API Key"
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Object not found, or not owned by this workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "Internal error. Retry, then contact Cartflox if it persists.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Internal Server Error"
            }
          }
        }
      },
      "PartnerUnauthorized": {
        "description": "Invalid partner secret.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid partner secret"
            }
          }
        }
      },
      "PartnerDisabled": {
        "description": "Partner API disabled on this server (shared secret not configured).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Partner API disabled (AFRIFLOW_PARTNER_SECRET not configured)"
            }
          }
        }
      },
      "MerchantNotFound": {
        "description": "No partner merchant with this identifier.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Marchand introuvable"
            }
          }
        }
      }
    },
    "examples": {
      "CheckoutSessionExample": {
        "summary": "Session created",
        "value": {
          "id": "cmf3k2p1x0001abcd9e8f7g6h",
          "object": "checkout.session",
          "url": "https://checkout.cartflox.com/cmf3k2p1x0001abcd9e8f7g6h",
          "order_id": "CS-MF3K2A-9X1QZ",
          "amount": 5000,
          "currency": "XOF",
          "status": "pending",
          "livemode": true,
          "created": "2026-09-02T10:15:00.000Z"
        }
      },
      "SessionStatusExample": {
        "summary": "Paid session",
        "value": {
          "id": "cmf3k2p1x0001abcd9e8f7g6h",
          "order_id": "CS-MF3K2A-9X1QZ",
          "status": "SUCCESS",
          "paid": true,
          "amount": 5000,
          "currency": "XOF",
          "provider": "PayDunya",
          "provider_reference": "pd_7f3a9c",
          "failure_message": null,
          "next_action": null,
          "customer_name": "Awa Koné",
          "customer_email": "awa@example.com",
          "customer_phone": "+2250700000000",
          "metadata": {
            "order_id": "1042",
            "source": "checkout_session"
          },
          "completed_at": "2026-09-02T10:17:42.000Z",
          "livemode": true
        }
      },
      "TransferExample": {
        "summary": "Transfer being processed",
        "value": {
          "id": "cmfx1a2b3c4d5e6f7g8h9i0j",
          "reference": "ddf9f0bf-f310-4126-83d9-dc8bc92b6e79",
          "status": "processing",
          "amount": 50000,
          "currency": "XOF",
          "fee": 0,
          "country": "CI",
          "operator": "orange_money",
          "provider_operator": "ORANGE_CIV",
          "phone": "2250712345678",
          "recipient_name": "Awa Koné",
          "description": "Salaire septembre",
          "provider": "PawaPay",
          "provider_reference": null,
          "failure": null,
          "idempotency_key": "salaire-2026-09-awa",
          "livemode": true,
          "created": "2026-09-18T09:12:41.000Z",
          "completed": null
        }
      },
      "WebhookDeliveryExample": {
        "summary": "Delivered on the first attempt",
        "value": {
          "id": "cmu8d4e5f0003abc",
          "object": "webhook.delivery",
          "event": "payment.completed",
          "url": "https://maboutique.com/webhooks/cartflox",
          "status": "delivered",
          "attempts": 2,
          "max_attempts": 10,
          "next_attempt_at": null,
          "last_status_code": 200,
          "last_error": null,
          "response_ms": 412,
          "transaction_id": "cmf3k2p1x0001abcd9e8f7g6h",
          "transfer_id": null,
          "livemode": true,
          "created_at": "2026-09-19T08:00:00.000Z",
          "delivered_at": "2026-09-19T08:01:03.000Z"
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Error body. `code` is present on transfer endpoints.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable code (transfers)."
          }
        },
        "additionalProperties": true
      },
      "Livemode": {
        "type": "boolean",
        "description": "`true` for a production object, `false` for a test object: created with a test key, or in a workspace not switched live yet. Verify webhook signatures with the secret key of the same mode."
      },
      "Currency": {
        "type": "string",
        "description": "ISO 4217 code. Accepted currencies are listed in the introduction. Amounts are whole numbers for currencies without minor units (XOF, XAF, GNF, UGX, RWF, CDF, MGA, KMF, DJF, BIF).",
        "enum": [
          "XOF",
          "XAF",
          "GHS",
          "NGN",
          "KES",
          "TZS",
          "UGX",
          "RWF",
          "ZMW",
          "MWK",
          "CDF",
          "ETB",
          "MZN",
          "ZAR",
          "EGP",
          "MAD",
          "GNF",
          "SLE",
          "LRD",
          "GMD",
          "MGA",
          "MUR",
          "USD",
          "EUR",
          "GBP"
        ]
      },
      "Metadata": {
        "type": "object",
        "description": "Your own free-form data, returned in webhooks. Cartflox adds `source` (`checkout_session`, `widget`, `payment_link`...) and, when relevant, `description`, `success_url`, `cancel_url` and `idempotencyKey`.",
        "additionalProperties": true
      },
      "TransactionStatus": {
        "type": "string",
        "description": "`PENDING`: created, not confirmed yet (customer on the page, OTP pending, redirection in progress).\n`SUCCESS`: confirmed by the aggregator. `FAILED`: refused or failed at the operator, the customer can retry on the same session.\n`CANCELLED`: cancelled, no longer payable. `REFUNDED`: refunded from the dashboard.\n",
        "enum": [
          "PENDING",
          "SUCCESS",
          "FAILED",
          "CANCELLED",
          "REFUNDED"
        ]
      },
      "CreateCheckoutSessionRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Amount, a whole positive number for currencies without minor units (5000 for 5 000 XOF)."
          },
          "currency": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Currency"
              }
            ],
            "default": "XOF"
          },
          "customer_name": {
            "type": "string",
            "description": "Customer name, pre-filled on the payment page."
          },
          "customer_email": {
            "type": "string",
            "format": "email",
            "description": "Customer e-mail (payment receipt)."
          },
          "customer_phone": {
            "type": "string",
            "description": "Phone number in E.164 international format (`+2250700000000`), pre-filled for Mobile Money."
          },
          "description": {
            "type": "string",
            "description": "Label shown to the customer."
          },
          "success_url": {
            "type": "string",
            "format": "uri",
            "description": "Return address after a successful payment."
          },
          "cancel_url": {
            "type": "string",
            "format": "uri",
            "description": "Return address when the customer gives up."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "merchant_name": {
            "type": "string",
            "maxLength": 60,
            "description": "Name shown at the top of the payment page instead of the workspace name (60 characters at most). Useful to platforms collecting for several brands."
          },
          "merchant_logo": {
            "type": "string",
            "format": "uri",
            "description": "`https` address of a logo shown instead of the workspace logo (PNG, JPG or SVG). Non-https values are ignored."
          }
        },
        "additionalProperties": true
      },
      "CheckoutSession": {
        "type": "object",
        "required": [
          "id",
          "object",
          "url",
          "order_id",
          "amount",
          "currency",
          "status",
          "livemode",
          "created"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Session identifier. Keep it to follow the payment."
          },
          "object": {
            "type": "string",
            "const": "checkout.session"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted payment page. Redirect your customer here."
          },
          "order_id": {
            "type": "string",
            "description": "Readable Cartflox reference (`CS-...`)."
          },
          "amount": {
            "type": "number",
            "description": "Amount the payer is asked for. On a Connect workspace with \"fees charged to the customer\", it is the requested amount plus the gateway fee."
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`pending` at creation. On an idempotent replay, the current status in lower case.",
            "enum": [
              "pending",
              "success",
              "failed",
              "cancelled",
              "refunded"
            ]
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "NextAction": {
        "type": "object",
        "description": "What the buyer still has to do (enter a code, dial a USSD code, follow a redirection), when the provider said so only after the initiation.",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "otp",
              "ussd",
              "redirection"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "ussd_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "application": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the mobile application to open, when relevant."
          }
        },
        "additionalProperties": true
      },
      "SessionStatus": {
        "type": "object",
        "required": [
          "id",
          "status",
          "paid",
          "amount",
          "currency",
          "order_id",
          "livemode"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "order_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "paid": {
            "type": "boolean",
            "description": "`true` only when `status` is `SUCCESS`."
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Gateway that processed the payment."
          },
          "provider_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reference at the aggregator, for your reconciliations."
          },
          "failure_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the payment failed, as told by the provider and translated."
          },
          "next_action": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NextAction"
              },
              {
                "type": "null"
              }
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Confirmation date, `null` until the payment succeeds."
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          }
        },
        "additionalProperties": true
      },
      "PaymentMethod": {
        "type": "object",
        "required": [
          "code",
          "gatewayId",
          "name"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Operator code at the gateway serving it (`ORANGE_CIV`, `wave-senegal`...). Read it, do not hard-code it."
          },
          "gatewayId": {
            "type": "string",
            "description": "Identifier of the gateway serving this method."
          },
          "name": {
            "type": "string"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Gateway name (`PayDunya`, `PawaPay`...)."
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country label, or `UEMOA`, `International`, `Global` for methods valid everywhere."
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "`MOBILE_MONEY`, `CARD` or `BANK_TRANSFER`."
          },
          "flag": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2 code or flag emoji, for display."
          },
          "logo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Logo path, relative to https://cartflox.com."
          }
        },
        "additionalProperties": true
      },
      "SessionMethods": {
        "type": "object",
        "required": [
          "id",
          "currency",
          "methods"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "methods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentMethod"
            }
          }
        },
        "additionalProperties": true
      },
      "CreatePaymentLinkRequest": {
        "type": "object",
        "required": [
          "title",
          "amount"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Title shown to the customer."
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Fixed amount."
          },
          "currency": {
            "type": "string",
            "default": "XOF"
          },
          "description": {
            "type": "string",
            "description": "Detail of the product or service."
          },
          "metadata": {
            "type": "object",
            "description": "Accepted by the API but not stored on the link at the moment.",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "PaymentLink": {
        "type": "object",
        "required": [
          "success",
          "id",
          "url",
          "slug",
          "amount",
          "currency"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "id": {
            "type": "string",
            "description": "Link identifier, to use with `/v1/payment-links/{id}/check`."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted page to share."
          },
          "slug": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "PaymentLinkCheck": {
        "type": "object",
        "required": [
          "success",
          "paid",
          "status"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "paid": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "description": "`SUCCESS` when paid; otherwise `WAITING` (no attempt yet) or the status of the latest non-failed attempt (`PENDING`, `CANCELLED`, `REFUNDED`).",
            "enum": [
              "SUCCESS",
              "WAITING",
              "PENDING",
              "CANCELLED",
              "REFUNDED"
            ]
          },
          "transaction": {
            "type": "object",
            "description": "Present when `paid` is `true`.",
            "required": [
              "id",
              "amount"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "customerName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "customerEmail": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ProviderKey": {
        "type": "string",
        "description": "Aggregator key as known by the routing engine.",
        "enum": [
          "paydunya",
          "pawapay",
          "flutterwave",
          "feexpay",
          "paystack",
          "cinetpay",
          "stripe",
          "kkiapay",
          "coinbase",
          "fedapay",
          "notchpay",
          "cryptomus",
          "qosic",
          "monetbill",
          "payplus",
          "hub2",
          "lengopay",
          "wave",
          "paytech",
          "onepay",
          "djamo",
          "ipay"
        ]
      },
      "RoutingMethod": {
        "type": "string",
        "description": "Payment method vocabulary of the routing engine.",
        "enum": [
          "card",
          "orange-money",
          "wave",
          "djamo",
          "mtn",
          "moov",
          "airtel",
          "free-money",
          "expresso",
          "wizall",
          "t-money",
          "mpesa",
          "mobile-money",
          "crypto"
        ]
      },
      "RoutingRequest": {
        "type": "object",
        "required": [
          "country",
          "amount",
          "availableProviders"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code."
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "paymentMethod": {
            "$ref": "#/components/schemas/RoutingMethod"
          },
          "phone": {
            "type": "string",
            "description": "Customer number; the operator is inferred from its prefix when `paymentMethod` is absent."
          },
          "availableProviders": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ProviderKey"
            },
            "description": "Candidate gateways."
          },
          "preferredProvider": {
            "$ref": "#/components/schemas/ProviderKey"
          }
        },
        "additionalProperties": true
      },
      "RoutingDecision": {
        "type": "object",
        "required": [
          "provider",
          "reason",
          "alternates",
          "confidence"
        ],
        "properties": {
          "provider": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "description": "Human-readable explanation (French)."
          },
          "alternates": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fallback providers, in order."
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        },
        "additionalProperties": true
      },
      "AvailableMethod": {
        "type": "object",
        "required": [
          "method",
          "providers"
        ],
        "properties": {
          "method": {
            "$ref": "#/components/schemas/RoutingMethod"
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "RankedProvider": {
        "type": "object",
        "required": [
          "provider",
          "score"
        ],
        "properties": {
          "provider": {
            "type": "string"
          },
          "score": {
            "type": "number"
          }
        },
        "additionalProperties": true
      },
      "RoutingResponse": {
        "type": "object",
        "required": [
          "routing",
          "detectedOperator",
          "availableMethods",
          "rankedProviders"
        ],
        "properties": {
          "routing": {
            "$ref": "#/components/schemas/RoutingDecision"
          },
          "detectedOperator": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/RoutingMethod"
              },
              {
                "type": "null"
              }
            ],
            "description": "Operator inferred from `phone`, or `null`."
          },
          "availableMethods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailableMethod"
            }
          },
          "rankedProviders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RankedProvider"
            }
          }
        },
        "additionalProperties": true
      },
      "RoutingOptions": {
        "type": "object",
        "required": [
          "country",
          "availableMethods",
          "rankedProviders"
        ],
        "properties": {
          "country": {
            "type": "string"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "availableMethods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailableMethod"
            }
          },
          "rankedProviders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RankedProvider"
            }
          }
        },
        "additionalProperties": true
      },
      "Transaction": {
        "type": "object",
        "description": "Raw transaction record, as exported by the dashboard.",
        "required": [
          "id",
          "orderId",
          "amount",
          "currency",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "applicationId": {
            "type": [
              "string",
              "null"
            ]
          },
          "orderId": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "paymentType": {
            "type": "string",
            "enum": [
              "MOBILE_MONEY",
              "CARD",
              "BANK_TRANSFER"
            ]
          },
          "provider": {
            "type": "string"
          },
          "providerRef": {
            "type": [
              "string",
              "null"
            ]
          },
          "customerName": {
            "type": "string"
          },
          "customerEmail": {
            "type": "string"
          },
          "customerPhone": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "TransactionExport": {
        "type": "object",
        "required": [
          "data",
          "total"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            }
          },
          "total": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "TransferStatus": {
        "type": "string",
        "description": "`pending`: created, not handed to the provider yet. `processing`: handed to the provider, the operator is processing it (the most frequent state right after the call).\n`succeeded`: the money reached the recipient, `completed` carries the date. `failed`: refused by the operator or the provider, `failure` says why, nothing was debited.\n",
        "enum": [
          "pending",
          "processing",
          "succeeded",
          "failed"
        ]
      },
      "TransferCountry": {
        "type": "string",
        "description": "Countries served for transfers (ISO 3166-1 alpha-2).",
        "enum": [
          "CI",
          "SN",
          "BJ",
          "BF",
          "TG",
          "ML",
          "NE",
          "GN",
          "CM",
          "GA",
          "CG",
          "CD",
          "GH",
          "NG",
          "KE",
          "TZ",
          "UG",
          "RW",
          "ZM"
        ]
      },
      "TransferOperator": {
        "type": "string",
        "description": "Cartflox operator vocabulary, common to every provider. Usual aliases (`mtn`, `orange`, `moov`, `MTN_MOMO_CIV`...) are accepted on input.",
        "enum": [
          "orange_money",
          "mtn_money",
          "moov_money",
          "wave",
          "free_money",
          "emoney",
          "expresso",
          "djamo",
          "tmoney",
          "celtiis",
          "mpesa",
          "airtel_money",
          "tigo_pesa",
          "halopesa",
          "vodacom",
          "zamtel",
          "vodafone_cash",
          "airtel_tigo",
          "express_union",
          "mobicash"
        ]
      },
      "CreateTransferRequest": {
        "type": "object",
        "required": [
          "amount",
          "country",
          "operator",
          "phone"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Amount received by the recipient, a whole number for francs (5000 for 5 000 XOF)."
          },
          "country": {
            "$ref": "#/components/schemas/TransferCountry"
          },
          "operator": {
            "type": "string",
            "description": "Recipient's operator (`orange_money`, `mtn_money`, `moov_money`, `wave`, `free_money`, `tmoney`, `mpesa`, `airtel_money`...). Aliases such as `mtn`, `orange`, `MTN_MOMO_CIV` are accepted. `method` and `network` are accepted as aliases of this field."
          },
          "phone": {
            "type": "string",
            "description": "Recipient's number, as dialled in the country (`0712345678`) or international (`+2250712345678`). `recipient_phone` and `msisdn` are accepted as aliases of this field."
          },
          "currency": {
            "type": "string",
            "description": "Inferred from the country (XOF, XAF, GHS, KES...). A currency that does not match the country is refused (`devise_invalide`)."
          },
          "recipient_name": {
            "type": "string",
            "maxLength": 120,
            "description": "Recipient name, for your statements and for the provider."
          },
          "description": {
            "type": "string",
            "maxLength": 200,
            "description": "Reason, forwarded when the operator shows it to the recipient."
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 120,
            "description": "Same role as the `Idempotency-Key` header."
          },
          "metadata": {
            "type": "object",
            "description": "Your own free-form data, kept with the transfer.",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "TransferFailure": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Transfer": {
        "type": "object",
        "required": [
          "id",
          "reference",
          "status",
          "amount",
          "currency",
          "fee",
          "country",
          "operator",
          "phone",
          "livemode",
          "created"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Cartflox identifier."
          },
          "reference": {
            "type": "string",
            "format": "uuid",
            "description": "Cartflox reference, sent to the provider. Also accepted by `GET /v1/transfers/{id}`."
          },
          "status": {
            "$ref": "#/components/schemas/TransferStatus"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "fee": {
            "type": "number",
            "description": "Cartflox fee. `0` with your own gateway keys."
          },
          "country": {
            "type": "string"
          },
          "operator": {
            "type": "string",
            "description": "Cartflox operator code (`orange_money`...)."
          },
          "provider_operator": {
            "type": [
              "string",
              "null"
            ],
            "description": "Operator code as the provider names it (`ORANGE_CIV`)."
          },
          "phone": {
            "type": "string",
            "description": "Digits only, country code included (`2250712345678`)."
          },
          "recipient_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "description": "Gateway that carries the transfer (`PawaPay`...)."
          },
          "provider_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reference at the provider, when it differs from `reference`."
          },
          "failure": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TransferFailure"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set when `status` is `failed`."
          },
          "idempotency_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "completed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "TransferList": {
        "type": "object",
        "required": [
          "data",
          "count"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            }
          },
          "count": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "TransferOptions": {
        "type": "object",
        "required": [
          "countries"
        ],
        "properties": {
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "code",
                "name",
                "currency",
                "operators"
              ],
              "properties": {
                "code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "currency": {
                  "type": "string"
                },
                "operators": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "code",
                      "name",
                      "provider"
                    ],
                    "properties": {
                      "code": {
                        "$ref": "#/components/schemas/TransferOperator"
                      },
                      "name": {
                        "type": "string"
                      },
                      "provider": {
                        "type": "string",
                        "description": "Gateway that will carry the transfer."
                      }
                    },
                    "additionalProperties": true
                  }
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "WebhookEventName": {
        "type": "string",
        "enum": [
          "payment.completed",
          "payment.failed",
          "payment.cancelled",
          "payment.updated",
          "transfer.succeeded",
          "transfer.failed"
        ]
      },
      "WebhookDeliveryStatus": {
        "type": "string",
        "enum": [
          "pending",
          "delivered",
          "failed"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "object",
          "event",
          "url",
          "status",
          "attempts",
          "max_attempts",
          "livemode",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "webhook.delivery"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEventName"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "status": {
            "$ref": "#/components/schemas/WebhookDeliveryStatus"
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "max_attempts": {
            "type": "integer",
            "const": 10
          },
          "next_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status of the last attempt."
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "response_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Duration of the last attempt, in milliseconds."
          },
          "transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Session id for payment events."
          },
          "transfer_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Transfer id for transfer events."
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "WebhookDeliveryList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "count"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of deliveries returned."
          }
        },
        "additionalProperties": true
      },
      "WebhookConfig": {
        "type": "object",
        "required": [
          "success",
          "webhookUrl"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "webhookUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Production webhook URL, `null` when none is set."
          },
          "testWebhookUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Test webhook URL, when one was set with `mode: test`."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventName"
            },
            "description": "Events the workspace receives. Every event when none was selected."
          },
          "available_events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventName"
            },
            "description": "Every event Cartflox can send."
          }
        },
        "additionalProperties": true
      },
      "UpdateWebhookConfigRequest": {
        "type": "object",
        "description": "At least one of `webhookUrl` and `events` is required.",
        "anyOf": [
          {
            "required": [
              "webhookUrl"
            ]
          },
          {
            "required": [
              "events"
            ]
          }
        ],
        "properties": {
          "webhookUrl": {
            "type": "string",
            "description": "Public `https` URL. An empty string removes the webhook."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "default": "live",
            "description": "`test` sets the URL that receives test events."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventName"
            },
            "description": "Events to receive. Omit for all events."
          }
        },
        "additionalProperties": true
      },
      "PaymentEventData": {
        "type": "object",
        "required": [
          "id",
          "order_id",
          "status",
          "amount",
          "currency"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Session identifier, the one returned at creation."
          },
          "order_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Metadata"
              },
              {
                "type": "null"
              }
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "`null` until the payment succeeds."
          }
        },
        "additionalProperties": true
      },
      "PaymentEvent": {
        "type": "object",
        "required": [
          "event",
          "livemode",
          "data",
          "timestamp"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "payment.completed",
              "payment.failed",
              "payment.cancelled",
              "payment.updated"
            ]
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          },
          "data": {
            "$ref": "#/components/schemas/PaymentEventData"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "TransferEvent": {
        "type": "object",
        "required": [
          "event",
          "livemode",
          "data",
          "timestamp"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "transfer.succeeded",
              "transfer.failed"
            ]
          },
          "livemode": {
            "$ref": "#/components/schemas/Livemode"
          },
          "data": {
            "$ref": "#/components/schemas/Transfer"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "SandboxSettleRequest": {
        "type": "object",
        "required": [
          "transactionId"
        ],
        "properties": {
          "transactionId": {
            "type": "string",
            "description": "Session identifier (`id` of the checkout session)."
          },
          "issue": {
            "type": "string",
            "enum": [
              "succes",
              "echec"
            ],
            "default": "succes",
            "description": "`succes` marks the payment `SUCCESS`, `echec` marks it `FAILED`."
          },
          "customerDetails": {
            "type": "object",
            "description": "Optional customer details written on the transaction.",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              }
            },
            "additionalProperties": true
          },
          "methodCode": {
            "type": "string",
            "maxLength": 80,
            "description": "Optional payment method code recorded in the metadata."
          }
        },
        "additionalProperties": true
      },
      "SandboxSettleResponse": {
        "type": "object",
        "required": [
          "success",
          "sandbox",
          "status",
          "message"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "sandbox": {
            "type": "boolean",
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "SUCCESS",
              "FAILED"
            ]
          },
          "message": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "SandboxError": {
        "type": "object",
        "description": "Error shape of the payment page endpoints.",
        "required": [
          "success",
          "message"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "message": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "CreatePartnerMerchantRequest": {
        "type": "object",
        "required": [
          "email",
          "shop_name"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Seller e-mail. An existing Cartflox account with this e-mail is reused."
          },
          "name": {
            "type": "string",
            "description": "Seller name (defaults to `Vendeur`)."
          },
          "shop_name": {
            "type": "string",
            "minLength": 2,
            "description": "Shop name, becomes the workspace name."
          },
          "whatsapp": {
            "type": "string",
            "description": "WhatsApp number of the seller."
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Webhook URL of the partner product for this merchant."
          }
        },
        "additionalProperties": true
      },
      "PartnerMerchant": {
        "type": "object",
        "required": [
          "application_id",
          "public_key",
          "secret_key",
          "payment_mode"
        ],
        "properties": {
          "application_id": {
            "type": "string"
          },
          "public_key": {
            "type": "string"
          },
          "secret_key": {
            "type": "string"
          },
          "payment_mode": {
            "type": "string",
            "description": "`own_keys` for merchants provisioned today."
          },
          "commission_bps": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cartflox commission in basis points, when set on the workspace."
          }
        },
        "additionalProperties": true
      },
      "PartnerBalance": {
        "type": "object",
        "required": [
          "balance",
          "pending_payouts",
          "available",
          "currency"
        ],
        "properties": {
          "balance": {
            "type": "number",
            "description": "Ledger balance."
          },
          "pending_payouts": {
            "type": "number"
          },
          "available": {
            "type": "number",
            "description": "`max(0, balance - pending_payouts)`."
          },
          "currency": {
            "type": "string",
            "const": "XOF"
          }
        },
        "additionalProperties": true
      },
      "PartnerPayoutStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "PAID",
          "REJECTED"
        ]
      },
      "PartnerPayoutMethod": {
        "type": "string",
        "enum": [
          "wave",
          "orange",
          "mtn",
          "moov",
          "bank"
        ]
      },
      "PartnerPayout": {
        "type": "object",
        "required": [
          "id",
          "amount",
          "currency",
          "status",
          "requestedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PartnerPayoutStatus"
          },
          "method": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipient": {
            "type": [
              "string",
              "null"
            ]
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time"
          },
          "processedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "PartnerPayoutList": {
        "type": "object",
        "required": [
          "payouts"
        ],
        "properties": {
          "payouts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerPayout"
            }
          }
        },
        "additionalProperties": true
      },
      "CreatePartnerPayoutRequest": {
        "type": "object",
        "required": [
          "amount",
          "method",
          "recipient"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "minimum": 1000,
            "description": "Whole number of francs, 1 000 at least, within the available balance."
          },
          "method": {
            "$ref": "#/components/schemas/PartnerPayoutMethod"
          },
          "recipient": {
            "type": "string",
            "maxLength": 60,
            "description": "Mobile Money number (8 digits at least) or bank account."
          }
        },
        "additionalProperties": true
      },
      "PartnerPayoutCreated": {
        "type": "object",
        "required": [
          "id",
          "status",
          "amount",
          "method",
          "recipient",
          "requested_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PartnerPayoutStatus"
          },
          "amount": {
            "type": "number"
          },
          "method": {
            "$ref": "#/components/schemas/PartnerPayoutMethod"
          },
          "recipient": {
            "type": "string"
          },
          "requested_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      }
    }
  }
}
