openapi: 3.0.3
info:
  title: Fiatsend Partner API
  description: |
    The Fiatsend Partner API enables platforms like Deel, Remote, and Payoneer to integrate stablecoin-to-mobile-money payouts for their users in Africa.

    Fiatsend converts stablecoins (USDC, USDT) into local fiat currency (GHS) and delivers funds directly to mobile money wallets (MTN MoMo, Telecel Cash, AirtelTigo Money) in seconds.

    ## Authentication

    All API requests require a Bearer token in the `Authorization` header:

    ```
    Authorization: Bearer your_api_key_here
    ```

    API keys are issued during partner onboarding. Contact partners@fiatsend.com to get started.

    ## Environments

    | Environment | Base URL |
    |-------------|----------|
    | Sandbox | `https://sandbox.fiatsend.com` |
    | Production | `https://api.fiatsend.com` |

    ## Rate Limits

    | Plan | Requests/min | Requests/day |
    |------|-------------|-------------|
    | Sandbox | 60 | 10,000 |
    | Production | 300 | 100,000 |

  version: 1.2.1
  contact:
    name: Fiatsend Engineering
    email: partners@fiatsend.com
    url: https://developer.fiatsend.com
  license:
    name: Proprietary
    url: https://fiatsend.com/terms

servers:
  - url: https://sandbox.fiatsend.com/v1
    description: Sandbox (testing)
  - url: https://api.fiatsend.com/v1
    description: Production

tags:
  - name: Withdrawals
    description: Create and manage stablecoin-to-mobile-money withdrawals
  - name: Rates
    description: Get real-time FX rates for stablecoin to GHS conversion
  - name: Networks
    description: Query supported mobile money networks and their status
  - name: Webhooks
    description: Register and manage webhook endpoints for status callbacks
  - name: Health
    description: Service health and status checks

paths:
  /withdrawals:
    post:
      tags: [Withdrawals]
      operationId: createWithdrawal
      summary: Create a withdrawal
      description: |
        Initiate a stablecoin-to-mobile-money withdrawal. Fiatsend locks the FX rate at the time of request, converts the stablecoin amount to GHS, and delivers funds to the specified mobile money wallet.

        The withdrawal goes through the following statuses:
        - `pending` — Request received, awaiting processing
        - `processing` — On-chain transaction submitted, mobile money transfer initiated
        - `completed` — Funds delivered to mobile money wallet
        - `failed` — Transaction failed (funds returned to source)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWithdrawalRequest'
            example:
              amount: "50.00"
              currency: "USDC"
              recipient_phone: "+233241234567"
              mobile_network: "MTN"
              reference_id: "deel-txn-abc123"
              metadata:
                contractor_id: "con_12345"
                invoice_id: "inv_67890"
      responses:
        '201':
          description: Withdrawal created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalResponse'
              example:
                status: "success"
                data:
                  withdrawal_id: "wd_9f8e7d6c5b4a"
                  status: "pending"
                  amount: "50.00"
                  currency: "USDC"
                  ghs_amount: "750.00"
                  fx_rate: "15.00"
                  fee: "0.50"
                  recipient_phone: "+233241234567"
                  mobile_network: "MTN"
                  reference_id: "deel-txn-abc123"
                  estimated_completion: "2026-03-21T07:00:00Z"
                  created_at: "2026-03-21T06:59:30Z"
        '400':
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: "error"
                code: "INVALID_PHONE"
                message: "Phone number must be a valid Ghana number starting with +233"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: "error"
                code: "BELOW_MINIMUM"
                message: "Minimum withdrawal amount is 5.00 USDC"
        '429':
          $ref: '#/components/responses/RateLimited'

  /withdrawals/{withdrawal_id}:
    get:
      tags: [Withdrawals]
      operationId: getWithdrawal
      summary: Get withdrawal status
      description: |
        Retrieve the current status and details of a withdrawal. Use this to poll for status updates or verify completion.

        For real-time updates, we recommend using webhooks instead of polling.
      parameters:
        - name: withdrawal_id
          in: path
          required: true
          schema:
            type: string
          example: "wd_9f8e7d6c5b4a"
      responses:
        '200':
          description: Withdrawal details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalDetailResponse'
              example:
                status: "success"
                data:
                  withdrawal_id: "wd_9f8e7d6c5b4a"
                  status: "completed"
                  amount: "50.00"
                  currency: "USDC"
                  ghs_amount: "750.00"
                  fx_rate: "15.00"
                  fee: "0.50"
                  recipient_phone: "+233241234567"
                  mobile_network: "MTN"
                  reference_id: "deel-txn-abc123"
                  on_chain_tx_hash: "0x1234abcd..."
                  mobile_money_reference: "MOMO-REF-456789"
                  created_at: "2026-03-21T06:59:30Z"
                  processing_at: "2026-03-21T06:59:35Z"
                  completed_at: "2026-03-21T06:59:42Z"
        '404':
          description: Withdrawal not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /transactions:
    get:
      tags: [Withdrawals]
      operationId: listTransactions
      summary: List transactions
      description: Retrieve a paginated list of transactions with optional filters.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, processing, completed, failed]
        - name: from_date
          in: query
          schema:
            type: string
            format: date-time
        - name: to_date
          in: query
          schema:
            type: string
            format: date-time
        - name: reference_id
          in: query
          schema:
            type: string
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 25
            maximum: 100
      responses:
        '200':
          description: Paginated transaction list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionListResponse'

  /rates:
    get:
      tags: [Rates]
      operationId: getRates
      summary: Get current FX rate
      description: |
        Get a real-time quote for converting stablecoins to GHS. The returned rate includes a `valid_until` timestamp — the rate is guaranteed until that time.

        Use this before creating a withdrawal to show the contractor what they'll receive.
      parameters:
        - name: from_currency
          in: query
          required: true
          schema:
            type: string
            enum: [USDC, USDT]
          example: "USDC"
        - name: to_currency
          in: query
          required: true
          schema:
            type: string
            enum: [GHS]
          example: "GHS"
        - name: amount
          in: query
          required: true
          schema:
            type: string
          example: "100.00"
      responses:
        '200':
          description: Current rate quote
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateResponse'
              example:
                status: "success"
                data:
                  from_currency: "USDC"
                  to_currency: "GHS"
                  amount: "100.00"
                  rate: "11.02"
                  fee: "1.00"
                  total_ghs: "1090.98"
                  valid_until: "2026-03-21T07:05:00Z"

  /supported-networks:
    get:
      tags: [Networks]
      operationId: getSupportedNetworks
      summary: List supported networks
      description: Returns all available mobile money networks with their current operational status.
      responses:
        '200':
          description: List of supported networks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NetworkListResponse'
              example:
                status: "success"
                data:
                  - network_id: "MTN"
                    name: "MTN MoMo"
                    country: "GH"
                    currency: "GHS"
                    status: "operational"
                    min_amount: "5.00"
                    max_amount: "10000.00"
                  - network_id: "TELECEL"
                    name: "Telecel Cash"
                    country: "GH"
                    currency: "GHS"
                    status: "operational"
                    min_amount: "5.00"
                    max_amount: "5000.00"
                  - network_id: "AIRTELTIGO"
                    name: "AirtelTigo Money"
                    country: "GH"
                    currency: "GHS"
                    status: "operational"
                    min_amount: "5.00"
                    max_amount: "5000.00"

  /limits:
    get:
      tags: [Networks]
      operationId: getLimits
      summary: Get transaction limits
      description: Returns transaction limits per KYC tier.
      responses:
        '200':
          description: Transaction limits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LimitsResponse'

  /webhooks:
    post:
      tags: [Webhooks]
      operationId: registerWebhook
      summary: Register a webhook
      description: |
        Register a URL to receive real-time status updates for withdrawals. Fiatsend sends POST requests to your URL with a JSON payload whenever a withdrawal status changes.

        All webhook payloads are signed with HMAC-SHA256. Verify the `X-Fiatsend-Signature` header using your webhook secret.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookRegistration'
            example:
              url: "https://api.deel.com/webhooks/fiatsend"
              events:
                - "withdrawal.completed"
                - "withdrawal.failed"
                - "withdrawal.processing"
              secret: "whsec_your_secret_here"
      responses:
        '201':
          description: Webhook registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookResponse'
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List registered webhooks
      responses:
        '200':
          description: List of webhooks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookListResponse'

  /webhooks/{webhook_id}:
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Delete a webhook
      parameters:
        - name: webhook_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Webhook deleted

  /health:
    get:
      tags: [Health]
      operationId: getHealth
      summary: Service health check
      description: Returns service status, uptime, and API version.
      security: []
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: "healthy"
                version: "1.0.0"
                uptime_seconds: 864000
                environment: "sandbox"
                services:
                  blockchain: "operational"
                  mobile_money: "operational"
                  database: "operational"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key

  responses:
    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            status: "error"
            code: "UNAUTHORIZED"
            message: "Invalid or missing API key"
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            status: "error"
            code: "RATE_LIMITED"
            message: "Rate limit exceeded. Try again in 60 seconds."

  schemas:
    CreateWithdrawalRequest:
      type: object
      required: [amount, currency, recipient_phone, mobile_network, reference_id]
      properties:
        amount:
          type: string
          description: Amount in source currency (e.g., "50.00")
        currency:
          type: string
          enum: [USDC, USDT]
          description: Source stablecoin currency
        recipient_phone:
          type: string
          description: "Recipient's mobile money phone number (E.164 format, e.g., +233241234567)"
        mobile_network:
          type: string
          enum: [MTN, TELECEL, AIRTELTIGO]
          description: Target mobile money network
        reference_id:
          type: string
          description: Your unique transaction reference (for idempotency)
        metadata:
          type: object
          additionalProperties: true
          description: Optional key-value metadata to attach to the withdrawal

    WithdrawalResponse:
      type: object
      properties:
        status:
          type: string
        data:
          $ref: '#/components/schemas/Withdrawal'

    WithdrawalDetailResponse:
      type: object
      properties:
        status:
          type: string
        data:
          $ref: '#/components/schemas/WithdrawalDetail'

    Withdrawal:
      type: object
      properties:
        withdrawal_id:
          type: string
        status:
          type: string
          enum: [pending, processing, completed, failed]
        amount:
          type: string
        currency:
          type: string
        ghs_amount:
          type: string
        fx_rate:
          type: string
        fee:
          type: string
        recipient_phone:
          type: string
        mobile_network:
          type: string
        reference_id:
          type: string
        estimated_completion:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time

    WithdrawalDetail:
      allOf:
        - $ref: '#/components/schemas/Withdrawal'
        - type: object
          properties:
            on_chain_tx_hash:
              type: string
              description: Blockchain transaction hash
            mobile_money_reference:
              type: string
              description: Mobile money network reference number
            processing_at:
              type: string
              format: date-time
            completed_at:
              type: string
              format: date-time
            failed_at:
              type: string
              format: date-time
            failure_reason:
              type: string

    TransactionListResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/WithdrawalDetail'
        pagination:
          $ref: '#/components/schemas/Pagination'

    RateResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: object
          properties:
            from_currency:
              type: string
            to_currency:
              type: string
            amount:
              type: string
            rate:
              type: string
            fee:
              type: string
            total_ghs:
              type: string
            valid_until:
              type: string
              format: date-time

    NetworkListResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/Network'

    Network:
      type: object
      properties:
        network_id:
          type: string
        name:
          type: string
        country:
          type: string
        currency:
          type: string
        status:
          type: string
          enum: [operational, degraded, down]
        min_amount:
          type: string
        max_amount:
          type: string

    LimitsResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: array
          items:
            type: object
            properties:
              tier:
                type: string
              daily_limit:
                type: string
              monthly_limit:
                type: string
              per_transaction_max:
                type: string

    WebhookRegistration:
      type: object
      required: [url, events, secret]
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum: [withdrawal.pending, withdrawal.processing, withdrawal.completed, withdrawal.failed]
        secret:
          type: string
          description: Secret for HMAC-SHA256 signature verification

    WebhookResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: object
          properties:
            webhook_id:
              type: string
            url:
              type: string
            events:
              type: array
              items:
                type: string
            active:
              type: boolean
            created_at:
              type: string
              format: date-time

    WebhookListResponse:
      type: object
      properties:
        status:
          type: string
        data:
          type: array
          items:
            type: object
            properties:
              webhook_id:
                type: string
              url:
                type: string
              events:
                type: array
                items:
                  type: string
              active:
                type: boolean

    HealthResponse:
      type: object
      properties:
        status:
          type: string
        version:
          type: string
          example: "1.0.0"
        uptime_seconds:
          type: integer
        environment:
          type: string
          description: Deployment environment (e.g. sandbox, production)
        services:
          type: object
          properties:
            blockchain:
              type: string
            mobile_money:
              type: string
            database:
              type: string

    ErrorResponse:
      type: object
      properties:
        status:
          type: string
          example: "error"
        code:
          type: string
        message:
          type: string

    Pagination:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer
        total_pages:
          type: integer

security:
  - BearerAuth: []
