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

# Initiate transfer

> Submits a token (or native) transfer from a derived wallet. **Asynchronous by default**: the request is validated, recorded, and answered `202` with the `PENDING` transaction while the on-chain execution continues. Poll [Get transaction by id](/api-reference/wallet/get-transaction-by-id) or pass `webhookUrl` to be notified of the outcome (see [Transfer lifecycle](/api-reference/wallet/overview#transfer-lifecycle-and-outcome-delivery)). Pass `waitForCompletion: true` for the legacy synchronous mode, which holds the request open (typically 3–30s) and answers `201` (completed) or `422` (failed). On EVM testnets gas can be sponsored; on mainnet gas is debited from the business USD balance. Requires the `wallet.transfer.create` capability and an `Idempotency-Key` header. See [Idempotency](/getting-started/conventions#idempotency).



## OpenAPI

````yaml POST /v1/wallet/transaction
openapi: 3.1.0
info:
  title: Convexity Infra API
  version: 1.0.0
  description: >-
    REST API reference for Convexity Infra: the backend services your business
    integrates to build and operate on-chain products. Every response is wrapped
    in a `{ status, message, data }` envelope and authorized with an OAuth 2.0
    bearer token minted by Auth-Edge.
  contact:
    name: Convexity Infra
    url: https://withconvexity.com
    email: infra@withconvexity.com
servers:
  - url: https://dev.api.withconvexity.com
    description: Development
security:
  - bearerAuth: []
tags:
  - name: Auth-Edge
    description: >-
      OAuth 2.0 token issuance. Exchange a project API key for a short-lived
      bearer token that authorizes every other Infra API call.
  - name: Projects
    description: >-
      Manage the calling project itself: rotate its API key pair and control
      which IP addresses may authenticate with it.
  - name: Payments
    description: >-
      List project and subscription payments, and mint virtual bank accounts
      (via KoraPay) for wallet top-ups and subscriptions.
  - name: Products
    description: >-
      Browse the products and capabilities available to your project. Each
      product maps to a plan and a set of capability keys.
  - name: Subscriptions
    description: List available plans and manage your project's subscriptions.
  - name: Wallet
    description: >-
      HD wallet lifecycle, balances, transfers, transaction signing, and
      supported-chain discovery. Wallets are scoped to the business in your
      token: accessing another business's wallet returns `403`.


      **Capabilities**: each operation is gated on a capability granted by your
      plan; a missing one returns `403: Missing required capability:
      <capability>`.


      | Capability | Grants |

      |---|---|

      | `wallet.hd.generate` | Generate wallets. |

      | `wallet.hd.read` | List and read wallets, counts, and HD status. |

      | `wallet.hd.sign` | Sign transactions. |

      | `wallet.hd.deactivate` | Deactivate a wallet. |

      | `wallet.transfer.create` | Initiate transfers. |

      | `wallet.transfer.read` | Read transaction history and records. |

      | `wallet.balance.read` | Read the USD balance. |
  - name: Indexer
    description: >-
      Subscribe to decoded on-chain events, receive signed webhooks, and inspect
      or replay delivery logs. Every endpoint requires a token with project +
      business context and a per-operation capability.
  - name: Tokenization
    description: >-
      Issue and manage tokenized real-world assets: create tokens, mint, burn,
      transfer, manage holders and registered wallets, and run yield operations
      (coupons, distributions, claims, and principal redemption).
paths:
  /v1/wallet/transaction:
    post:
      tags:
        - Wallet
      summary: Initiate transfer
      description: >-
        Submits a token (or native) transfer from a derived wallet.
        **Asynchronous by default**: the request is validated, recorded, and
        answered `202` with the `PENDING` transaction while the on-chain
        execution continues. Poll [Get transaction by
        id](/api-reference/wallet/get-transaction-by-id) or pass `webhookUrl` to
        be notified of the outcome (see [Transfer
        lifecycle](/api-reference/wallet/overview#transfer-lifecycle-and-outcome-delivery)).
        Pass `waitForCompletion: true` for the legacy synchronous mode, which
        holds the request open (typically 3–30s) and answers `201` (completed)
        or `422` (failed). On EVM testnets gas can be sponsored; on mainnet gas
        is debited from the business USD balance. Requires the
        `wallet.transfer.create` capability and an `Idempotency-Key` header. See
        [Idempotency](/getting-started/conventions#idempotency).
      operationId: initiateTransfer
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
          example: 0c1d2e3f-4a5b-6c7d-8e9f-001122334455
          description: >-
            Unique client-generated key (use a UUID) that makes this request
            safe to retry. A retry sent with the same key returns the original
            result instead of submitting again. See
            [Idempotency](/getting-started/conventions#idempotency).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - fromAddress
                - toAddress
                - amount
                - chainType
              properties:
                fromAddress:
                  type:
                    - string
                    - integer
                  description: >-
                    Sending wallet: either a complete wallet address (e.g.
                    `0x71C7…976F`) or its HD `addressIndex` (e.g. `12`).
                toAddress:
                  type: string
                  description: Recipient address.
                amount:
                  type: string
                  description: >-
                    Amount in the token's major unit, as a decimal string (e.g.
                    `"150.00"` for 150 USDC).
                chainType:
                  type: string
                  enum:
                    - EVM
                    - SOLANA
                chainId:
                  type: integer
                  description: >-
                    Target chain id. Use `84532` (Base Sepolia) for sponsored
                    testnet transfers. Required for EVM.
                tokenAddress:
                  type: string
                  description: ERC-20 token contract. Omit for a native transfer.
                cNGN:
                  type: boolean
                  description: >-
                    Optional. Set to `true` to transfer cNGN. Defaults to
                    `false`.
                waitForCompletion:
                  type: boolean
                  description: >-
                    Optional. `true` = legacy synchronous mode: hold the request
                    open for the final outcome (`201`/`422`) instead of `202` +
                    poll. Defaults to `false`.
                webhookUrl:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: >-
                    Optional. POST the final transaction here once execution
                    settles (`wallet.transfer.completed` /
                    `wallet.transfer.failed`). HTTPS only in production;
                    private/internal hosts rejected. Delivery is signed and
                    retried. See [Outcome
                    webhook](/api-reference/wallet/overview#outcome-webhook).
                webhookSecret:
                  type: string
                  minLength: 16
                  maxLength: 256
                  description: >-
                    Optional (requires `webhookUrl`). Your own HMAC key for the
                    webhook signature: verify deliveries with the value you
                    sent. Held in memory only; never persisted or logged.
            examples:
              byAddressIndex:
                summary: From HD addressIndex (async + webhook)
                value:
                  fromAddress: 12
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  chainType: EVM
                  chainId: 8453
                  network: MAINNET
                  cNGN: true
                  webhookUrl: https://example.com/hooks/wallet
                  webhookSecret: a-secret-you-generate-and-keep
              byAddress:
                summary: From full wallet address
                value:
                  fromAddress: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  chainType: EVM
                  chainId: 8453
                  network: MAINNET
                  cNGN: true
              waitForCompletion:
                summary: Legacy synchronous mode
                value:
                  fromAddress: 12
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  chainType: EVM
                  chainId: 8453
                  network: MAINNET
                  cNGN: true
                  waitForCompletion: true
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
              example:
                status: true
                message: Transfer submitted successfully
                data:
                  id: 9a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9
                  type: TRANSFER
                  chainType: EVM
                  network: MAINNET
                  chainId: 8453
                  signerType: BUSINESS_HD
                  fromAddress: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
                  addressIndex: 12
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  valueUsd: '150.00'
                  tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
                  data: null
                  value: null
                  txHash: >-
                    0x9d8c7b6a5e4f3c2b1a0987654321fedcba9876543210abcdef1234567890abcd
                  status: COMPLETED
                  gasUsed: '70152'
                  feeWei: '12030000000000'
                  explorerUrl: https://basescan.org
                  explorerTxUrl: >-
                    https://basescan.org/tx/0x9d8c7b6a5e4f3c2b1a0987654321fedcba9876543210abcdef1234567890abcd
                  createdAt: '2026-07-03T08:14:02.771Z'
                  updatedAt: '2026-07-03T08:14:09.010Z'
        '202':
          description: >-
            Accepted (default): the transfer is recorded and executing. Poll
            `GET /v1/wallet/transaction/{transactionId}` until `status` is
            `COMPLETED` or `FAILED`, or receive the outcome at `webhookUrl`.
          content:
            application/json:
              example:
                status: true
                message: Transfer accepted
                data:
                  id: 9a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9
                  type: TRANSFER
                  chainType: EVM
                  network: MAINNET
                  chainId: 8453
                  signerType: BUSINESS_HD
                  fromAddress: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
                  addressIndex: 12
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  tokenAddress: '0x52828daa48C1a9A06F37500882b42daf0bE04C3B'
                  data: null
                  value: null
                  txHash: null
                  status: PENDING
                  gasUsed: null
                  feeWei: null
                  explorerUrl: https://basescan.org
                  explorerTxUrl: null
                  createdAt: '2026-07-10T19:00:00.000Z'
                  updatedAt: '2026-07-10T19:00:00.000Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: 'chainId: chainId is required for EVM transfers'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: Authorization token is missing or malformed.
        '402':
          description: Payment required
          content:
            application/json:
              schema:
                type: object
              examples:
                402_Payment_Required_insufficient_balance:
                  summary: 402 Payment Required – insufficient balance
                  value:
                    status: false
                    message: >-
                      Insufficient balance to cover the gas fee for this
                      transfer
                402_Payment_Required_no_active_subscription:
                  summary: 402 Payment Required – no active subscription
                  value:
                    status: false
                    message: No subscription found for this product's plan
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                type: object
              examples:
                403_Forbidden_test_token:
                  summary: 403 Forbidden – test token
                  value:
                    status: false
                    message: Test tokens are not allowed, please use your live api key.
                403_Forbidden_capability_not_granted:
                  summary: 403 Forbidden – capability not granted
                  value:
                    status: false
                    message: Capability wallet.transfer.create not granted
        '404':
          description: Not found
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: No active RPC config found for chain
        '422':
          description: Unprocessable entity
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: Transfer failed
                data:
                  id: 9a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9
                  type: TRANSFER
                  chainType: EVM
                  network: MAINNET
                  chainId: 8453
                  signerType: BUSINESS_HD
                  fromAddress: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
                  addressIndex: 12
                  toAddress: '0x2222222222222222222222222222222222222222'
                  amount: '150.00'
                  valueUsd: '150.00'
                  tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
                  data: null
                  value: null
                  txHash: null
                  status: FAILED
                  gasUsed: null
                  feeWei: null
                  explorerUrl: https://basescan.org
                  explorerTxUrl: null
                  createdAt: '2026-07-03T08:14:02.771Z'
                  updatedAt: '2026-07-03T08:14:09.010Z'
                  error: 'execution reverted: transfer amount exceeds balance'
        '429':
          description: Too many requests
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: Too many requests. Please try again later.
                retryAfter: 60
        '503':
          description: Service unavailable
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: Gas price could not be determined; transfer rejected
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth 2.0 access token from Auth-Edge.

````