> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sandpay.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a disbursement

> Free-form payout (merchant → an msisdn), not linked to any prior
collection. Debits the merchant float, credits the recipient SIM. No
commission is taken. Emits a `disbursement.completed` webhook.

Disbursements are persisted as transactions with `type=disbursement`;
retrieve them via `GET /v1/payments/{id}` and list them with
`GET /v1/payments?type=disbursement`.




## OpenAPI

````yaml /openapi/sandpay.yaml post /v1/disbursements
openapi: 3.1.0
info:
  title: SandPay API
  version: 0.5.3
  description: |
    Official public API for the SandPay Mobile Money sandbox. Simulate MTN,
    Orange, Moov, and Airtel payment flows across African markets without
    touching real money rails. The API is deterministic, low-latency, and
    designed to mirror the production behaviour of each operator so client
    integrations can be exercised end-to-end before going live.
  contact:
    name: SandPay Support
    url: https://docs.sandpay.dev
    email: support@sandpay.dev
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://api.sandpay.dev
    description: Production
  - url: https://api.sandbox.sandpay.dev
    description: Sandbox
security:
  - bearerAuth: []
tags:
  - name: Payments
    description: Create, retrieve, list, and refund simulated Mobile Money payments.
  - name: Disbursements
    description: Free-form payouts (merchant → an msisdn), not linked to a collection.
  - name: Meta
    description: API version + capability discovery for keeping integrations current.
  - name: Health
    description: Service health probe.
paths:
  /v1/disbursements:
    post:
      tags:
        - Disbursements
      summary: Create a disbursement
      description: |
        Free-form payout (merchant → an msisdn), not linked to any prior
        collection. Debits the merchant float, credits the recipient SIM. No
        commission is taken. Emits a `disbursement.completed` webhook.

        Disbursements are persisted as transactions with `type=disbursement`;
        retrieve them via `GET /v1/payments/{id}` and list them with
        `GET /v1/payments?type=disbursement`.
      operationId: createDisbursement
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DisbursementInput'
      responses:
        '200':
          description: |
            Disbursement created (status SUCCESS, INSUFFICIENT_FUNDS,
            UNKNOWN_MSISDN, or ACCOUNT_BLOCKED).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Disbursement'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Environment not found for the given country/operator.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    DisbursementInput:
      type: object
      required:
        - amount
        - currency
        - operator
        - country
        - msisdn
        - application
      properties:
        amount:
          type: integer
          minimum: 1
          example: 5000
        currency:
          type: string
          minLength: 3
          maxLength: 4
          example: XOF
        operator:
          $ref: '#/components/schemas/PaymentOperator'
        country:
          type: string
          minLength: 2
          maxLength: 2
          example: CI
        msisdn:
          type: string
          pattern: ^\+[1-9][0-9]{6,14}$
          example: '+22507123456'
        application:
          type: string
          minLength: 1
          maxLength: 80
          description: Your store's slug (Settings → Stores). Required.
          example: my-store
        reference:
          type: string
          minLength: 1
          maxLength: 80
          description: Optional idempotency reference. Auto-generated when omitted.
        order_ref:
          type: string
          minLength: 1
          maxLength: 120
          description: >-
            Optional order id grouping related transactions. Falls back to
            `reference` (B126).
        order_url:
          type: string
          format: uri
          maxLength: 500
          description: Optional public URL to the order in your app (B126).
        is_url_protected:
          type: boolean
          description: >-
            Set `true` when `order_url` requires a specific app permission to
            open (B130).
        description:
          type: string
          maxLength: 200
          description: Optional free-form description (max 200 chars).
          example: Cashback reward
    Disbursement:
      type: object
      required:
        - tx_id
        - type
        - status
        - amount
      properties:
        tx_id:
          type: string
          example: TX_DB7788AA
        type:
          type: string
          enum:
            - disbursement
        status:
          $ref: '#/components/schemas/PaymentStatus'
        amount:
          type: integer
          minimum: 1
          example: 5000
        merchant_balance_after:
          type: number
          description: Merchant float after the disbursement.
        recipient_balance_after:
          type: number
          nullable: true
          description: Recipient SIM balance after the credit, or null.
        idempotent:
          type: boolean
          description: True when this disbursement already existed (idempotent replay).
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: >-
            Machine-readable error code (e.g. `invalid_body`, `unauthorized`,
            `quota_exceeded`).
          example: quota_exceeded
        message:
          type: string
          description: Human-readable description of what went wrong.
          example: Monthly simulation quota exceeded.
        detail:
          type: object
          nullable: true
          description: Optional structured detail, e.g. field validation errors.
          additionalProperties: true
    PaymentOperator:
      type: string
      enum:
        - mtn
        - orange
        - moov
        - airtel
      description: Mobile Money operator code.
    PaymentStatus:
      type: string
      enum:
        - SUCCESS
        - PIN_INVALID
        - INSUFFICIENT_FUNDS
        - TIMEOUT
        - ACCOUNT_BLOCKED
        - USER_CANCELLED
        - UNKNOWN_MSISDN
        - LIMIT_EXCEEDED
        - SERVICE_UNAVAILABLE
        - DUPLICATE_REFERENCE
        - PENDING
      description: Final settlement status.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key in format `sp_sk_test_...` or `sp_sk_live_...`.

````