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

# Create Transaction

> Create a new transaction to process a payment for a customer. Use `type: WITHDRAW` to send funds to a destination payment method (requires `destinationId`), `type: DEPOSIT` to pull funds from a source payment method (requires `sourceId`), or `type: SWAP` to convert funds between currencies within the Afriex wallet (requires `sourceCurrency`, `destinationCurrency`, `meta`, and exactly one of `sourceAmount` or `destinationAmount`).


Create a new transaction to process a payment for a customer. Use `type: WITHDRAW` to send funds to a destination payment method (requires `destinationId`), `type: DEPOSIT` to pull funds from a source payment method (requires `sourceId`), or `type: SWAP` to convert funds between currencies within the Afriex wallet. A SWAP requires `sourceCurrency`, `destinationCurrency`, `meta`, and **exactly one** of `sourceAmount` or `destinationAmount`: the API computes the other side at the live exchange rate. Providing both amounts is rejected.

<Note>
  **Testing in sandbox?** Sandbox transactions settle automatically within \~1-2 minutes and send the `TRANSACTION.UPDATED` webhook. Set `meta.reference` to a value containing `fail` to test a **FAILED** outcome, or any other value for **SUCCESS**. To exercise OTP-required deposits, add `SIMULATE_OTP` to `meta.reference` and complete with [Authorize Transaction](/api-reference/endpoint/transactions/authorize). See [Testing transaction outcomes in sandbox](/development#testing-transaction-outcomes-in-sandbox).
</Note>


## OpenAPI

````yaml POST /api/v1/transaction
openapi: 3.1.0
info:
  title: Afriex Business API
  version: 1.0.8
  description: >-
    Welcome to the Afriex Business API. This API allows you to manage customers,
    process payments, handle payouts, and receive real-time notifications via
    webhooks.


    For detailed guidance on authentication, pagination, error handling, and
    webhooks, please refer to the [dedicated guides](https://docs.afriex.com) in
    the top bar. The guide provides a step-by-step instructions to help you
    integrate seamlessly.
  termsOfService: https://www.afriex.com/terms-and-condition
  contact:
    name: Afriex API Support
    email: eng@afriex.co
    url: https://docs.afriex.com
  license:
    name: Proprietary
    url: https://www.afriex.com/terms-and-condition
servers:
  - url: https://sandbox.api.afriex.com
    description: Staging Base URL
  - url: https://api.afriex.com
    description: Production Base URL
security:
  - ApiKey: []
tags:
  - name: Customers
    description: Create and manage your customers.
  - name: Payment Methods
    description: Register and resolve customer payout and collection methods.
  - name: Transactions
    description: Create and track deposits, withdrawals, and swaps.
  - name: Balance
    description: View and top up your business wallet balances.
  - name: Rates
    description: Fetch real-time exchange rates.
  - name: Checkout Sessions
    description: Create hosted checkout sessions.
  - name: Webhooks
    description: Webhook event payloads and sandbox webhook testing.
  - name: Media
    description: Generate presigned URLs for secure file uploads.
paths:
  /api/v1/transaction:
    parameters:
      - $ref: '#/components/parameters/x-api-version'
    post:
      tags:
        - Transactions
      summary: Create a new transaction
      description: >
        Create a new transaction to process a payment for a customer. Use `type:
        WITHDRAW` to send funds to a destination payment method (requires
        `destinationId`), `type: DEPOSIT` to pull funds from a source payment
        method (requires `sourceId`), or `type: SWAP` to convert funds between
        currencies within the Afriex wallet (requires `sourceCurrency`,
        `destinationCurrency`, `meta`, and exactly one of `sourceAmount` or
        `destinationAmount`).
      operationId: createTransaction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customerId:
                  type: string
                  description: >-
                    The unique identifier of the customer. Required for DEPOSIT
                    and WITHDRAW transactions. Not required for SWAP, if
                    omitted, the transaction runs against the business wallet.
                  examples:
                    - 69528240ba52c13b669fb239
                type:
                  type: string
                  description: >-
                    The type of transaction. DEPOSIT pulls funds from the
                    customer's source payment method (requires sourceId).
                    WITHDRAW sends funds to the customer's destination payment
                    method (requires destinationId). SWAP converts funds between
                    currencies within the Afriex wallet (requires
                    sourceCurrency, destinationCurrency, meta, and exactly one
                    of sourceAmount or destinationAmount; the API computes the
                    other side at the live exchange rate, and sending both is
                    rejected). If omitted, defaults to WITHDRAW.
                  enum:
                    - DEPOSIT
                    - WITHDRAW
                    - SWAP
                sourceAmount:
                  $ref: '#/components/schemas/amount'
                  description: >-
                    The transaction amount in the source currency. For WITHDRAW,
                    provide exactly one of sourceAmount or destinationAmount:
                    sending sourceAmount alone lets the API compute
                    destinationAmount at the live forward rate (useful when you
                    know only how much you want to send, e.g. a SWIFT payout of
                    a fixed source balance). For DEPOSIT the required amount is
                    destinationAmount, so sourceAmount is optional. For SWAP,
                    provide exactly one of sourceAmount or destinationAmount:
                    setting sourceAmount makes the API compute destinationAmount
                    at the live exchange rate.
                destinationAmount:
                  $ref: '#/components/schemas/amount'
                  description: >-
                    The transaction amount in the destination currency. Required
                    for DEPOSIT. For WITHDRAW, provide exactly one of
                    sourceAmount or destinationAmount: setting destinationAmount
                    fixes the payout amount, while omitting it and sending
                    sourceAmount lets the API compute destinationAmount at the
                    live forward rate. For SWAP, provide exactly one of
                    sourceAmount or destinationAmount: setting destinationAmount
                    instead makes the API compute sourceAmount at the inverse
                    rate. Sending both amounts on a SWAP is rejected.
                destinationCurrency:
                  type: string
                  description: >-
                    The 3-letter ISO 4217 currency code of the destination
                    currency (e.g., USD, NGN).
                  examples:
                    - USD
                sourceCurrency:
                  type: string
                  description: >-
                    The 3-letter ISO 4217 currency code of the source currency
                    (e.g., USD, NGN).
                  examples:
                    - NGN
                destinationId:
                  type: string
                  description: >
                    Required for WITHDRAW transactions. The id of the
                    destination payment method that money will be sent to.
                  examples:
                    - 690df3281c11eea59108fcaf
                sourceId:
                  type: string
                  description: >
                    Required for DEPOSIT transactions. The id of the source
                    payment method that money will be pulled from.
                  examples:
                    - 690df3281c11eea59108fcaf
                meta:
                  $ref: '#/components/schemas/TransactionMeta'
                  description: >-
                    Transaction metadata. idempotencyKey and reference are
                    required.
                shouldPreferSourceAmount:
                  type: boolean
                  description: >-
                    Opt in to deriving destinationAmount from sourceAmount even
                    when both amounts are sent. Defaults to false, which keeps
                    destination-wins semantics so a caller that echoes both
                    amounts still gets the destinationAmount they asked for. Set
                    true to have sourceAmount drive the payout via the forward
                    rate.
                correspondentBankName:
                  type: string
                  description: >-
                    The correspondent (intermediary) bank name for USD payouts
                    (WITHDRAW with destinationCurrency USD and meta.settlement
                    'request'). Only used as a fallback when the destination
                    payment method lacks it — the payment method's value takes
                    precedence. Must be provided together with
                    correspondentBankAccountNumber.
                correspondentBankAccountNumber:
                  type: string
                  description: >-
                    The correspondent (intermediary) bank account number for USD
                    payouts (WITHDRAW with destinationCurrency USD and
                    meta.settlement 'request'). Only used as a fallback when the
                    destination payment method lacks it — the payment method's
                    value takes precedence. Must be provided together with
                    correspondentBankName.
              oneOf:
                - title: Withdraw
                  description: >-
                    Send funds to a customer's destination payment method. This
                    is the default when type is omitted. Provide exactly one of
                    sourceAmount or destinationAmount; sending sourceAmount
                    alone lets the API compute the destination side at the live
                    forward rate.
                  required:
                    - customerId
                    - sourceCurrency
                    - destinationCurrency
                    - destinationId
                    - meta
                  properties:
                    type:
                      enum:
                        - WITHDRAW
                - title: Deposit
                  description: Pull funds from a customer's source payment method.
                  required:
                    - type
                    - customerId
                    - destinationAmount
                    - sourceCurrency
                    - destinationCurrency
                    - sourceId
                    - meta
                  properties:
                    type:
                      enum:
                        - DEPOSIT
                - title: Swap
                  description: >-
                    Convert funds between currencies within the Afriex wallet.
                    Provide exactly one of sourceAmount or destinationAmount;
                    sending both is rejected.
                  required:
                    - type
                    - sourceCurrency
                    - destinationCurrency
                    - meta
                  properties:
                    type:
                      enum:
                        - SWAP
            examples:
              Withdraw:
                summary: >-
                  Withdraw (destination amount): fix the payout amount the
                  customer receives
                value:
                  customerId: 69528240ba52c13b669fb239
                  type: WITHDRAW
                  destinationAmount: 5000
                  sourceCurrency: USD
                  destinationCurrency: NGN
                  destinationId: 690df3281c11eea59108fcaf
                  meta:
                    reference: ref-withdraw-001
                    idempotencyKey: idem-withdraw-001
              WithdrawSourceAmount:
                summary: >-
                  Withdraw (source amount, SWIFT MZN→USD): send the full source
                  balance and let the API compute the payout at the live forward
                  rate
                value:
                  customerId: 69528240ba52c13b669fb239
                  type: WITHDRAW
                  sourceAmount: 10000
                  sourceCurrency: MZN
                  destinationCurrency: USD
                  destinationId: 690df3281c11eea59108fcaf
                  meta:
                    reference: ref-withdraw-002
                    idempotencyKey: idem-withdraw-002
              Deposit:
                summary: Deposit, pull funds from a customer's source payment method
                value:
                  customerId: 69528240ba52c13b669fb239
                  type: DEPOSIT
                  sourceAmount: '10'
                  destinationAmount: 5000
                  sourceCurrency: USD
                  destinationCurrency: NGN
                  sourceId: 690df3281c11eea59108fcaf
                  meta:
                    reference: ref-deposit-001
                    idempotencyKey: idem-deposit-001
              Swap:
                summary: >-
                  Swap (source-only): convert funds between currencies within
                  the Afriex wallet
                value:
                  type: SWAP
                  sourceAmount: 10
                  sourceCurrency: USD
                  destinationCurrency: NGN
                  meta:
                    reference: ref-swap-001
                    idempotencyKey: idem-swap-001
              SwapDestination:
                summary: >-
                  Swap (destination-only): the API computes the source amount at
                  the inverse rate
                value:
                  type: SWAP
                  destinationAmount: 16500
                  sourceCurrency: USD
                  destinationCurrency: NGN
                  meta:
                    reference: ref-swap-002
                    idempotencyKey: idem-swap-002
      responses:
        '201':
          description: Transaction created successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Transaction'
              examples:
                withdraw:
                  summary: WITHDRAW transaction created (pending)
                  value:
                    data:
                      status: PENDING
                      type: WITHDRAW
                      channel: BANK_ACCOUNT
                      sourceAmount: '3.28847'
                      sourceCurrency: USD
                      destinationAmount: '5000'
                      destinationCurrency: NGN
                      destinationId: 690df3281c11eea59108fcaf
                      customerId: 69528240ba52c13b669fb239
                      transactionId: 69d60071ab82306f11b03393
                      meta:
                        reference: ref-withdraw-001
                        idempotencyKey: idem-withdraw-001
                      createdAt: '2026-04-08T07:14:57.444Z'
                      updatedAt: '2026-04-08T07:14:57.444Z'
                swap:
                  summary: SWAP transaction created (success)
                  value:
                    data:
                      status: SUCCESS
                      type: SWAP
                      channel: WALLET
                      sourceAmount: '10'
                      sourceCurrency: USD
                      destinationAmount: '14101.041'
                      destinationCurrency: NGN
                      destinationId: ''
                      customerId: ''
                      transactionId: 69d60072ab82306f11b033b9
                      meta:
                        reference: ref-swap-001
                        idempotencyKey: idem-swap-001
                      createdAt: '2026-04-08T07:14:58.237Z'
                      updatedAt: '2026-04-08T07:14:58.237Z'
        '400':
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                swapBothAmounts:
                  summary: SWAP with both amounts provided
                  value:
                    code: INVALID_BUSINESS_TRANSACTION_REQUEST
                    error: Invalid business transaction request
                    details:
                      errorMessage: Invalid business transaction request
                      friendlyMessage: >-
                        Only one of source amount or destination amount can be
                        provided
                missingDestinationAmount:
                  summary: Missing destination amount
                  value:
                    code: INVALID_BUSINESS_TRANSACTION_REQUEST
                    error: Invalid business transaction request
                    details:
                      errorMessage: Invalid business transaction request
                      friendlyMessage: Invalid destination amount
                invalidCurrency:
                  summary: Invalid currency code
                  value:
                    code: INVALID_BUSINESS_TRANSACTION_REQUEST
                    error: Invalid business transaction request
                    details:
                      errorMessage: Invalid business transaction request
                      friendlyMessage: Invalid source currency
                missingIdempotencyKey:
                  summary: Missing idempotency key
                  value:
                    code: INVALID_BUSINESS_TRANSACTION_REQUEST
                    error: Invalid business transaction request
                    details:
                      errorMessage: Invalid business transaction request
                      friendlyMessage: No idempotency key provided
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingApiKey:
                  summary: Missing API key
                  value:
                    code: AUTHENTICATION_ERROR
                    error: Authorization header is missing
                    details: {}
                invalidApiKey:
                  summary: Invalid API key
                  value:
                    code: AUTHENTICATION_ERROR
                    error: Invalid authorization header
                    details: {}
        '404':
          description: Not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                customerNotFound:
                  summary: Customer not found
                  value:
                    code: USER_NOT_FOUND
                    error: User not found
                    details:
                      errorMessage: User not found
                      friendlyMessage: ''
        '500':
          description: Server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                serverError:
                  summary: Unexpected server error
                  value:
                    code: INTERNAL_SERVER_ERROR
                    error: It's not you, it's us, please reach out to support
                    details: {}
      x-codeSamples:
        - lang: TypeScript
          label: Afriex SDK
          source: |
            const transaction = await afriex.transactions.create({
              customerId: "customer-id",
              sourceAmount: "10",
              destinationAmount: "5000",
              sourceCurrency: "USD",
              destinationCurrency: "NGN",
              destinationId: "payment-method-id",
              meta: {
                idempotencyKey: "unique-key-123",
                reference: "order-456",
                narration: "Payment for services",
              },
            });
components:
  parameters:
    x-api-version:
      name: x-api-version
      in: header
      required: false
      description: >-
        API version in ISO 8601 format (e.g. 2025-12-28). Defaults to latest
        stable.
      schema:
        type: string
  schemas:
    amount:
      description: >-
        A monetary amount. Accepts a number or a numeric string; responses
        return amounts as strings.
      oneOf:
        - type: number
        - type: string
      examples:
        - '100.50'
    TransactionMeta:
      type: object
      properties:
        narration:
          type: string
          description: The optional reason for the transaction.
        invoice:
          type: string
          description: >-
            The optional invoice filename (S3 object key) for the transaction,
            as returned by the media upload endpoint.
        idempotencyKey:
          type: string
          description: The idempotency key for the transaction.
        reference:
          type: string
          description: The reference for the transaction.
        settlement:
          type: string
          enum:
            - spot
            - request
          description: >-
            Optional settlement handling for the transaction. `spot` (the
            default) processes immediately and debits your Payout wallet.
            `request` is only supported for WITHDRAW transactions: it debits
            your Collection wallet (rather than the default Payout wallet) and
            queues the payout for the next business day in the destination
            payment method's country. Omit `settlement` (or send `spot`) for an
            ordinary payout from the Payout wallet.
      required:
        - idempotencyKey
        - reference
    Transaction:
      type: object
      properties:
        transactionId:
          type: string
          description: The unique identifier for the transaction.
        customerId:
          type: string
          description: The unique identifier of the customer.
        sourceId:
          type: string
          description: The unique identifier of the source payment method.
        destinationId:
          type: string
          description: The unique identifier of the destination payment method.
        sourceAmount:
          type: string
          description: The souce transaction amount value
        sourceCurrency:
          type: string
          description: The currency code of the wallet charged.
        destinationAmount:
          type: string
          description: The destination transaction amount value
        destinationCurrency:
          type: string
          description: The description currency code.
        type:
          type: string
          enum:
            - DEPOSIT
            - WITHDRAW
            - SWAP
          description: The type of transaction.
        channel:
          type: string
          enum:
            - BANK_ACCOUNT
            - SWIFT
            - MOBILE_MONEY
            - UPI
            - INTERAC
            - WE_CHAT
            - CARD
            - CRYPTO
            - VIRTUAL_BANK_ACCOUNT
            - POOL_ACCOUNT
            - ACH_BANK_ACCOUNT
            - PAYBILL_TILL
            - RFP
            - VIRTUAL_CARD
            - ALIPAY
            - WALLET
          description: The payment channel of the transaction.
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - SUCCESS
            - FAILED
            - CANCELLED
            - REFUNDED
            - RETRY
            - UNKNOWN
            - SCHEDULED
            - CUSTOMER_ACTION_REQUIRED
            - REJECTED
            - IN_REVIEW
            - DISPUTED
            - DISPUTE_RESOLVED
            - DISPUTE_WON
            - DISPUTE_LOST
            - DISPUTE_EVIDENCE_SUBMITTED
          description: The current status of the transaction.
        merchantReference:
          type: string
          description: >-
            The merchant-supplied reference for the transaction (mirrors
            meta.reference from the create request).
        rate:
          type: string
          description: >-
            The realized source-to-destination exchange rate for the
            transaction, expressed as `1 sourceCurrency = rate
            destinationCurrency` (equal to destinationAmount / sourceAmount).
        fee:
          type: string
          description: >-
            The fee charged for this transaction, denominated in
            `sourceCurrency`. It is reported separately from `sourceAmount`.
            Omitted when no fee applied to the transaction.
        meta:
          type: object
          description: >-
            Transaction metadata. Echoes the metadata you attached on create and
            may include server-set state flags such as `otpRequired` and
            `failureReason`.
          properties:
            narration:
              type: string
              description: >-
                The narration you attached when creating the transaction, echoed
                back. An empty string when none was provided.
            otpRequired:
              type: boolean
              description: >-
                Returned on deposits that may need an extra authorization step.
                When `true`, the deposit is waiting for the customer to submit a
                one-time password; call `POST
                /transaction/{transactionId}/authorize` to complete it.
            failureReason:
              type: object
              description: >-
                Present only when `status` is `FAILED` or `REJECTED`. Carries a
                stable `AFX_*` code and a customer-safe message. Branch on
                `code` rather than the underlying rail so your integration stays
                stable across routing changes.
              required:
                - code
                - message
                - retryable
              properties:
                code:
                  type: string
                  description: >-
                    Stable `AFX_*` failure code. Safe to switch on; the set
                    grows over time but existing values do not change meaning.
                  example: AFX_VELOCITY_LIMIT_EXCEEDED
                  enum:
                    - AFX_REQUEST_FAILED
                    - AFX_SYSTEM_ERROR
                    - AFX_SERVICE_UNAVAILABLE
                    - AFX_INVALID_CURRENCY
                    - AFX_INVALID_AMOUNT
                    - AFX_INVALID_RECIPIENT
                    - AFX_RECIPIENT_NOT_FOUND
                    - AFX_BENEFICIARY_RESTRICTED
                    - AFX_INVALID_SENDER
                    - AFX_INVALID_REQUEST
                    - AFX_VELOCITY_LIMIT_EXCEEDED
                    - AFX_AMOUNT_LIMIT_EXCEEDED
                    - AFX_PAYMENT_FAILED
                    - AFX_COMPLIANCE_REJECTED
                    - AFX_PROPOSAL_EXPIRED
                message:
                  type: string
                  description: >-
                    Customer-safe short description of the failure. Suitable for
                    display; do not parse — branch on `code` instead.
                retryable:
                  type: boolean
                  description: >-
                    `true` when re-submitting the same request (or, for
                    `AFX_PROPOSAL_EXPIRED`, starting a fresh proposal) may
                    succeed. `false` when the caller must change the request
                    before retrying.
          additionalProperties: true
        createdAt:
          type: string
          description: The date and time the transaction was created.
        updatedAt:
          type: string
          description: The date and time the transaction was last updated.
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: Machine-readable error code.
        error:
          type: string
          description: Human-readable error message.
        details:
          $ref: '#/components/schemas/ErrorDetails'
    ErrorDetails:
      type: object
      properties:
        errorMessage:
          type: string
          description: Detailed/technical error message.
        friendlyMessage:
          type: string
          description: User-facing error message safe to display.
        data:
          type: object
          description: >-
            Optional caller-safe context for the error. On a customer-create
            uniqueness conflict (EMAIL_ALREADY_EXISTS /
            PHONE_NUMBER_ALREADY_EXISTS) this carries the existing customer's
            id, so you can adopt it without a follow-up lookup.
          properties:
            customerId:
              type: string
              description: Id of the existing customer (on a create conflict).
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Static business API key issued from the dashboard. A business can
        provision **multiple API keys**, each scoped to a configurable set of
        **permissions** (e.g. read transactions, create deposits, etc).
        Permissions are chosen per key at creation time in the dashboard and may
        be revoked by deleting the key. Requests made with a key that does not
        include the permission required by the target endpoint will be rejected
        with a `403 Forbidden` response; an unrecognised, malformed or revoked
        key returns `401 Unauthorized`. Manage your keys and their permissions
        under **Developer → API keys** in the dashboard.

````