> ## 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.

# Create deposit account

> Mints a one-time deposit account to fund a wallet top-up or a subscription, depending on `purpose`. The request body is a discriminated union on `purpose`. By default an account is minted for **every** active settlement rail (you may get one FIAT bank account *and* one STABLECOIN deposit wallet back); pass `providerType` to pin a single rail. A free ($0) plan is activated immediately and returns an empty `data` array (no account needed).

`data` is **always an array** of account objects, each a discriminated union on `kind`: a `FIAT` account carries bank keys only, a `STABLECOIN` account carries on-chain keys only. Branch on `kind`; do not assume a field exists across both rails.

**FX rate lock**: every amount in the response is priced at a single NGN/USD rate, returned as `fxRate` (USD per 1 NGN). The rate is locked until `expiryAt`: the deposit settles at the quoted rate even if the market moves inside the deposit window.

**Stablecoin peg**: a stablecoin quote is denominated in the token's peg currency (`pegCurrency`). USD-pegged tokens (USDC) quote the USD equivalent; **cNGN is 1:1 with the naira**, so its `amountExpected` *is* the naira amount and `amountExpectedNgn` carries the naira value to display to the payer.

**Upgrades are prorated**: if the project already holds an active paid subscription to a different plan of the *same product*, the unused value of that subscription (`current plan price × fraction of the period remaining`) is credited against the new plan's price and only the difference is charged. All response amounts already reflect the credit. On settlement the old subscription is cancelled and its product access is replaced by the new plan's. Moving from a **free** plan works the same way with no credit (full price, free subscription and grant replaced). If the credit would cover the entire new price (a downgrade), the request is rejected. Renewals need no special field: send the same `planId` again and the existing subscription is renewed.



## OpenAPI

````yaml POST /v1/payments/subscribe
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://api.convexity.tools
    description: Production
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/payments/subscribe:
    post:
      tags:
        - Payments
      summary: Create deposit account
      description: >-
        Mints a one-time deposit account to fund a wallet top-up or a
        subscription, depending on `purpose`. The request body is a
        discriminated union on `purpose`. By default an account is minted for
        **every** active settlement rail (you may get one FIAT bank account
        *and* one STABLECOIN deposit wallet back); pass `providerType` to pin a
        single rail. A free ($0) plan is activated immediately and returns an
        empty `data` array (no account needed).


        `data` is **always an array** of account objects, each a discriminated
        union on `kind`: a `FIAT` account carries bank keys only, a `STABLECOIN`
        account carries on-chain keys only. Branch on `kind`; do not assume a
        field exists across both rails.


        **FX rate lock**: every amount in the response is priced at a single
        NGN/USD rate, returned as `fxRate` (USD per 1 NGN). The rate is locked
        until `expiryAt`: the deposit settles at the quoted rate even if the
        market moves inside the deposit window.


        **Stablecoin peg**: a stablecoin quote is denominated in the token's peg
        currency (`pegCurrency`). USD-pegged tokens (USDC) quote the USD
        equivalent; **cNGN is 1:1 with the naira**, so its `amountExpected` *is*
        the naira amount and `amountExpectedNgn` carries the naira value to
        display to the payer.


        **Upgrades are prorated**: if the project already holds an active paid
        subscription to a different plan of the *same product*, the unused value
        of that subscription (`current plan price × fraction of the period
        remaining`) is credited against the new plan's price and only the
        difference is charged. All response amounts already reflect the credit.
        On settlement the old subscription is cancelled and its product access
        is replaced by the new plan's. Moving from a **free** plan works the
        same way with no credit (full price, free subscription and grant
        replaced). If the credit would cover the entire new price (a downgrade),
        the request is rejected. Renewals need no special field: send the same
        `planId` again and the existing subscription is renewed.
      operationId: createDepositAccount
      parameters:
        - name: providerType
          in: query
          schema:
            type: string
            enum:
              - FIAT
              - STABLECOIN
          description: >-
            Restrict settlement to one rail. Omit to mint an account for every
            active provider.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - purpose
              properties:
                purpose:
                  type: string
                  enum:
                    - WALLET_TOPUP
                    - SUBSCRIPTION
                  description: Settlement target for the funded account.
                amount:
                  type: number
                  description: >-
                    Wallet top-up: positive amount in USD major units (e.g. `15`
                    = $15). The account is bound to this exact amount.
                planId:
                  type: integer
                  description: >-
                    Subscription: positive plan id to subscribe to. Renewals and
                    upgrades are resolved server-side from this; no subscription
                    id is sent.
                billingCycle:
                  type: string
                  enum:
                    - MONTHLY
                    - YEARLY
                  description: 'Subscription: **required**. Sizes the paid period.'
                description:
                  type: string
                  description: Free-text note (max 500 chars).
            example:
              purpose: SUBSCRIPTION
              planId: 3
              billingCycle: MONTHLY
              description: Subscribe to Pro plan
      responses:
        '200':
          description: 'OK: one account per active provider (empty for a free plan).'
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: boolean
                  message:
                    type: string
                  data:
                    type: array
                    description: >-
                      One account per active provider. Empty (`[]`) for a free
                      ($0) plan.
                    items:
                      oneOf:
                        - type: object
                          title: FiatAccount
                          description: >-
                            Bank-transfer deposit account. Carries bank keys
                            only.
                          required:
                            - kind
                            - accountNumber
                            - accountName
                            - bankName
                            - bankCode
                          properties:
                            kind:
                              type: string
                              enum:
                                - FIAT
                              description: Rail discriminant.
                            accountNumber:
                              type: string
                              description: Virtual bank account number to pay into.
                            accountName:
                              type: string
                              description: Account holder name.
                            bankName:
                              type: string
                              description: Bank the account is held with.
                            bankCode:
                              type: string
                              description: Bank code.
                            reference:
                              type: string
                              description: Reference tying the funding to this account.
                            amountExpected:
                              type: number
                              description: Amount to pay, in NGN.
                            fxRate:
                              type: number
                              description: >-
                                NGN/USD rate (USD per 1 NGN) the quote was
                                priced at, locked until `expiryAt`; settlement
                                uses this rate.
                            expiryAt:
                              type: string
                              format: date-time
                              description: ISO 8601 expiry. Pay before this time (~1h).
                        - type: object
                          title: StablecoinAccount
                          description: On-chain deposit wallet. Carries on-chain keys only.
                          required:
                            - kind
                            - walletAddress
                          properties:
                            kind:
                              type: string
                              enum:
                                - STABLECOIN
                              description: Rail discriminant.
                            walletAddress:
                              type: string
                              description: >-
                                Temporary on-chain deposit address to send
                                tokens to.
                            token:
                              type: string
                              description: Token symbol (e.g. `USDC`, `cNGN`).
                            pegCurrency:
                              type: string
                              enum:
                                - USD
                                - NGN
                              description: >-
                                Fiat currency the token is pegged to: `USD`
                                (USDC) or `NGN` (cNGN, 1:1 naira).
                            contractAddress:
                              type: string
                              description: Token contract address.
                            network:
                              type: string
                              description: Chain family (e.g. `EVM`).
                            blockchainNetwork:
                              type: string
                              description: Human-readable network name (e.g. `Base`).
                            chainId:
                              type: integer
                              description: EVM chain id (e.g. `8453`).
                            reference:
                              type: string
                              description: Reference tying the funding to this wallet.
                            amountExpected:
                              type: number
                              description: >-
                                Amount to send, in token major units (e.g. `15`
                                = 15 USDC). Denominated in the token's peg
                                currency.
                            amountExpectedNgn:
                              type: number
                              description: >-
                                Naira value to display to the payer. Present
                                only for naira-pegged tokens (cNGN), where it
                                equals `amountExpected`.
                            fxRate:
                              type: number
                              description: >-
                                NGN/USD rate (USD per 1 NGN) the quote was
                                priced at, locked until `expiryAt`; settlement
                                uses this rate.
                            expiryAt:
                              type: string
                              format: date-time
                              description: ISO 8601 expiry. Send before this time (~30m).
                      discriminator:
                        propertyName: kind
              examples:
                fiat:
                  summary: 'FIAT: bank transfer'
                  value:
                    status: true
                    message: Virtual account created successfully
                    data:
                      - kind: FIAT
                        accountNumber: '0123456789'
                        accountName: Acme Ltd / Konga
                        bankName: Providus Bank
                        bankCode: '001'
                        reference: ref-1
                        amountExpected: 7500
                        fxRate: 0.000665
                        expiryAt: '2026-07-03T12:00:00.000Z'
                stablecoin:
                  summary: 'STABLECOIN: USD-pegged (USDC)'
                  value:
                    status: true
                    message: Deposit wallet created successfully
                    data:
                      - kind: STABLECOIN
                        walletAddress: '0x9aE4f2b1c3D5e6F7089a1B2c3D4e5F60718293aA'
                        token: USDC
                        pegCurrency: USD
                        contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
                        network: EVM
                        blockchainNetwork: Base
                        chainId: 8453
                        reference: ref-2
                        amountExpected: 15
                        fxRate: 0.000665
                        expiryAt: '2026-07-03T11:30:00.000Z'
                stablecoinCngn:
                  summary: 'STABLECOIN: naira-pegged (cNGN)'
                  value:
                    status: true
                    message: Deposit wallet created successfully
                    data:
                      - kind: STABLECOIN
                        walletAddress: '0xEf6943Cc49A77EE4008e4228A96ca64d9D04a01D'
                        token: cNGN
                        pegCurrency: NGN
                        contractAddress: '0xe2387F04d3858e7Cb64Ef5Ed6617f9B2fcEEAfa2'
                        network: EVM
                        blockchainNetwork: Base
                        chainId: 8453
                        reference: ref-3
                        amountExpected: 22556.39
                        amountExpectedNgn: 22556.39
                        fxRate: 0.000665
                        expiryAt: '2026-07-03T11:30:00.000Z'
                freePlan:
                  summary: 'Free plan: activated, no account'
                  value:
                    status: true
                    message: Subscription created successfully
                    data: []
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                type: object
              examples:
                400_Invalid_request_body:
                  summary: '400: Invalid request body'
                  value:
                    status: false
                    message: 'planId: Required'
                400_Duplicate_active_subscription:
                  summary: '400: Duplicate active subscription'
                  value:
                    status: false
                    message: Project already has an active subscription for plan 3
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
              example:
                status: false
                message: Invalid token
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth 2.0 access token from Auth-Edge.

````