openapi: 3.1.0
info:
  title: SwApp Float API V2
  version: 2.0.0
  summary: Versioned float APIs with explicit HTTP status codes and numeric SwApp status values.
servers:
  - url: https://www.swapp.co.ug/swapp/api/v2
    description: Production
  - url: https://www.swapp.co.ug/apitest/v2
    description: SIT
tags:
  - name: Float
    description: Agent float validation, top-up, withdrawal, and status APIs.
paths:
  /float/token:
    post:
      tags: [Float]
      operationId: createFloatTokenV2
      summary: Create a bearer token for V2 float requests.
      security:
        - clientBasicAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
      responses:
        '200':
          description: Token created.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/TokenData'
              examples:
                success:
                  value:
                    code: 200
                    status: 1
                    message: SUCCESS
                    data:
                      clientId: '37596'
                      accessToken: eyJhbGciOiJIUzI1NiIs...
                      tokenType: Bearer
                      expiresIn: 36600
                      expiresAt: 1788249600
        '401':
          $ref: '#/components/responses/AuthenticationError'
  /float/agents/validate:
    post:
      tags: [Float]
      operationId: validateFloatAgentV2
      summary: Validate an active SwApp agent account.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentValidationRequest'
      responses:
        '200':
          description: Agent validated.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/AgentValidationData'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/AuthorizationError'
  /float/top-ups:
    post:
      tags: [Float]
      operationId: createFloatTopUpV2
      summary: Credit agent float after a bank-side deposit.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
        - $ref: '#/components/parameters/SwappTimestamp'
        - $ref: '#/components/parameters/SwappNonce'
        - $ref: '#/components/parameters/SwappSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FloatTopUpRequest'
      responses:
        '200':
          description: Float top-up posted successfully.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      requestId:
                        type: string
                      data:
                        $ref: '#/components/schemas/FloatPaymentData'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/AuthorizationError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '429':
          $ref: '#/components/responses/RateLimitError'
  /float/withdrawals/validate:
    post:
      tags: [Float]
      operationId: validateFloatWithdrawalV2
      summary: Validate an agent withdrawal request before app approval is created.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawalValidationRequest'
      responses:
        '200':
          description: Withdrawal request validated.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/WithdrawalValidationData'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/AuthorizationError'
        '409':
          $ref: '#/components/responses/ConflictError'
  /float/withdrawals:
    post:
      tags: [Float]
      operationId: createFloatWithdrawalV2
      summary: Create an agent withdrawal request requiring app approval.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawalRequest'
      responses:
        '200':
          description: Withdrawal approved and completed.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      requestId:
                        type: string
                      data:
                        $ref: '#/components/schemas/WithdrawalPaymentData'
        '202':
          description: Withdrawal request is pending agent approval.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiEnvelope'
                  - type: object
                    properties:
                      requestId:
                        type: string
                      data:
                        $ref: '#/components/schemas/PendingPaymentData'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/AuthorizationError'
        '408':
          $ref: '#/components/responses/ExpiredError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '412':
          $ref: '#/components/responses/PreconditionError'
  /float/status:
    post:
      tags: [Float]
      operationId: getFloatStatusV2
      summary: Check float top-up or withdrawal status by request ID.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SwappClientId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StatusRequest'
      responses:
        '200':
          description: Completed transaction found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiEnvelope'
        '202':
          description: Transaction is pending.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiEnvelope'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/AuthenticationError'
        '403':
          $ref: '#/components/responses/AuthorizationError'
        '404':
          $ref: '#/components/responses/NotFoundError'
components:
  securitySchemes:
    clientBasicAuth:
      type: http
      scheme: basic
      description: Basic credentials are user_key:user_secret and require Swapp-Client-ID.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    SwappClientId:
      name: Swapp-Client-ID
      in: header
      required: true
      schema:
        type: string
      description: API client identifier.
    SwappTimestamp:
      name: X-Swapp-Timestamp
      in: header
      required: false
      schema:
        type: integer
      description: Unix timestamp required when signed requests are enabled.
    SwappNonce:
      name: X-Swapp-Nonce
      in: header
      required: false
      schema:
        type: string
      description: Unique nonce required when signed requests are enabled.
    SwappSignature:
      name: X-Swapp-Signature
      in: header
      required: false
      schema:
        type: string
        pattern: '^sha256=[a-fA-F0-9]{64}$'
      description: HMAC-SHA256 signature required when signed requests are enabled.
  responses:
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            missingField:
              value:
                code: 400
                status: 8
                message: 'Missing required field: agentNumber'
                data:
                  errors:
                    agentNumber:
                      - The agentNumber field is required.
    AuthenticationError:
      description: Authentication failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            invalidToken:
              value:
                code: 401
                status: 9
                message: Invalid or expired bearer token
                data: {}
    AuthorizationError:
      description: API capability is not enabled or the client cannot access the resource.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            capabilityDisabled:
              value:
                code: 403
                status: 10
                message: Float API access is not enabled for this client
                data: {}
    ConflictError:
      description: Duplicate, replay, or conflicting request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            duplicateRequest:
              value:
                code: 409
                status: 6
                message: Duplicate requestId
                requestId: d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6
                data: {}
    ExpiredError:
      description: Agent approval request expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            approvalExpired:
              value:
                code: 408
                status: 5
                message: Agent approval request expired
                requestId: d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6
                data: {}
    NotFoundError:
      description: Transaction not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            transactionNotFound:
              value:
                code: 404
                status: 11
                message: Transaction not found
                requestId: d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6
                data: {}
    PreconditionError:
      description: Required prior validation is missing or expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            validationRequired:
              value:
                code: 412
                status: 14
                message: Run withdrawal validation before creating this request
                data: {}
    RateLimitError:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiEnvelope'
          examples:
            tooManyRequests:
              value:
                code: 429
                status: 12
                message: Too many requests. Please retry later.
                data: {}
  schemas:
    ApiEnvelope:
      type: object
      required: [code, status, message]
      properties:
        code:
          type: integer
          description: HTTP status code returned by the API.
          examples: [200]
        status:
          $ref: '#/components/schemas/SwappStatus'
        message:
          type: string
          examples: [SUCCESS]
        requestId:
          type: string
          format: uuid
        data:
          type: object
    SwappStatus:
      type: integer
      description: Numeric SwApp application status.
      enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
      x-enumDescriptions:
        1: SUCCESS
        2: PENDING
        3: FAILED
        4: REJECTED
        5: EXPIRED
        6: DUPLICATE
        7: BLOCKED_REPLAY
        8: VALIDATION_ERROR
        9: AUTHENTICATION_ERROR
        10: AUTHORIZATION_ERROR
        11: NOT_FOUND
        12: RATE_LIMITED
        13: SYSTEM_ERROR
        14: PRECONDITION_REQUIRED
    AgentValidationRequest:
      type: object
      required: [agentNumber]
      properties:
        agentNumber:
          type: string
          minLength: 1
          examples: ['37291']
    FloatTopUpRequest:
      type: object
      required: [agentNumber, amount, requestId, bankReference]
      properties:
        agentNumber:
          type: string
          minLength: 1
        amount:
          type: integer
          minimum: 1
          maximum: 50000000
        requestId:
          type: string
          format: uuid
        bankReference:
          type: string
          minLength: 1
          maxLength: 64
    WithdrawalValidationRequest:
      type: object
      required: [agentNumber, amount]
      properties:
        agentNumber:
          type: string
          minLength: 1
        amount:
          type: integer
          minimum: 1000
          maximum: 5000000
        channel:
          type: string
          maxLength: 40
        wallet:
          $ref: '#/components/schemas/Wallet'
    WithdrawalRequest:
      type: object
      required: [agentNumber, amount, requestId, channel]
      properties:
        agentNumber:
          type: string
          minLength: 1
        amount:
          type: integer
          minimum: 1000
          maximum: 5000000
        requestId:
          type: string
          format: uuid
        channel:
          type: string
          minLength: 1
          maxLength: 40
        reference:
          type: string
          maxLength: 64
        narration:
          type: string
          maxLength: 120
        timeout:
          type: integer
          minimum: 0
          maximum: 120
          default: 60
        wallet:
          $ref: '#/components/schemas/Wallet'
    StatusRequest:
      type: object
      required: [requestId]
      properties:
        requestId:
          type: string
          format: uuid
        agentNumber:
          type: string
          minLength: 1
    Wallet:
      type: string
      enum: [credit, swapp, swapp_wallet, momo_credit, mtn, mtn_wallet, airtel, airtel_wallet]
    TokenData:
      type: object
      required: [clientId, accessToken, tokenType, expiresIn, expiresAt]
      properties:
        clientId:
          type: string
        accessToken:
          type: string
        tokenType:
          type: string
          enum: [Bearer]
        expiresIn:
          type: integer
        expiresAt:
          type: integer
    Agent:
      type: object
      properties:
        agentNumber:
          type: string
        name:
          type: string
        business:
          type: string
        address:
          type: string
        pos:
          type: string
        status:
          $ref: '#/components/schemas/SwappStatus'
    Validation:
      type: object
      properties:
        status:
          $ref: '#/components/schemas/SwappStatus'
        expiresIn:
          type: integer
    Payment:
      type: object
      properties:
        transactionId:
          type: integer
        requestId:
          type: string
        bankReference:
          type: string
        date:
          type: string
        status:
          $ref: '#/components/schemas/SwappStatus'
    AgentValidationData:
      type: object
      properties:
        agent:
          $ref: '#/components/schemas/Agent'
        validation:
          $ref: '#/components/schemas/Validation'
    WithdrawalValidationData:
      allOf:
        - $ref: '#/components/schemas/AgentValidationData'
        - type: object
          properties:
            amount:
              type: object
              properties:
                amount:
                  type: integer
                charge:
                  type: integer
                total:
                  type: integer
            liquidation:
              type: object
              properties:
                wallet:
                  type: string
                channel:
                  type: string
    FloatPaymentData:
      type: object
      properties:
        agent:
          $ref: '#/components/schemas/Agent'
        amount:
          type: object
          properties:
            paid:
              type: integer
            float:
              type: integer
            charge:
              type: integer
        payment:
          $ref: '#/components/schemas/Payment'
        review:
          type: object
    WithdrawalPaymentData:
      type: object
      properties:
        agent:
          $ref: '#/components/schemas/Agent'
        amount:
          type: object
          properties:
            amount:
              type: integer
            liquidated:
              type: integer
            charge:
              type: integer
        liquidation:
          type: object
          properties:
            channel:
              type: string
            wallet:
              type: string
            account:
              type: string
            reference:
              type: string
            narration:
              type: string
        payment:
          $ref: '#/components/schemas/Payment'
        transactionHash:
          type: string
    PendingPaymentData:
      type: object
      properties:
        agent:
          $ref: '#/components/schemas/Agent'
        payment:
          $ref: '#/components/schemas/Payment'
