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

# Issue Bank Account

> Issues an NGN or USD deposit account for an approved account. The provider is chosen automatically based on currency and account type. NGN accounts are issued immediately. USD accounts require provider review: the first call submits the account's application data to the provider and returns `202`; call again after approval to collect the deposit account details.

Issues an **NGN** or **USD** deposit account for an approved account. The banking provider is selected automatically based on the currency and whether the account is a business or an individual.

The details each provider needs are prefilled from the account's application. Check [Get Issuance Requirements](/api-reference/endpoint/accounts/bank-account-requirements) first to see the prefilled payload and anything missing, then pass the missing pieces in `accountData`.

## NGN accounts

NGN accounts are issued **immediately**.

```bash theme={null}
curl -X POST "https://api.rolla.xyz/api/v1/external/accounts/eec3cbed-79d8-4370-87a0-b6be9e287337/bank-accounts" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "NGN",
    "accountData": {
      "bvn": "22211122233"
    }
  }'
```

```json theme={null}
{
  "success": true,
  "message": "Bank account issued successfully",
  "data": {
    "status": "issued",
    "bankAccount": {
      "id": "7f3a2b10-91c4-4f3b-b1d2-0a8e44c10a55",
      "currency": "NGN",
      "bankName": "Guaranty Trust Bank",
      "accountNumber": "1238726395",
      "accountName": "Beta Logistics LLC",
      "type": "static",
      "status": "active"
    }
  }
}
```

## USD accounts

<Info>
  **Every USD account we open is a real bank account** — the first one and every one after it. Each has its own account number, in the client's name, at the same bank. There is no "physical" tier and no "virtual" tier: accounts opened later are not virtual accounts layered over the first.

  You will see the term anyway, because our storage for deposit accounts is called *virtual accounts* — which is why the webhook announcing even a client's **first** USD account is [`account.virtual_account.created`](/api-reference/webhooks/events). That is an internal name, not a product distinction.

  The only real distinction is **primary** vs **additional**, and it is visible on every account as the `reference` field: `null` on the primary, set on the ones you named. See [Multiple USD deposit accounts](#multiple-usd-deposit-accounts) below.
</Info>

USD accounts require **review**. The first call records the request and returns `202 Accepted`; the deposit account is set up once the request is approved:

```bash theme={null}
curl -X POST "https://api.rolla.xyz/api/v1/external/accounts/eec3cbed-79d8-4370-87a0-b6be9e287337/bank-accounts" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USD",
    "accountData": {
      "onboardingDetails": {
        "businessInfo": { "tradeName": "Beta Logistics", "website": "https://betalogistics.com" }
      },
      "representatives": [{
        "relatedPersonId": "a3d4f21e-f499-488f-8508-43228dfea485",
        "taxNumber": "123-45-6789",
        "idDocument": {
          "number": "P123456789",
          "type": "passport",
          "issueDate": "2020-01-01",
          "expirationDate": "2030-01-01"
        }
      }]
    }
  }'
```

```json theme={null}
{
  "success": true,
  "message": "Bank account request accepted",
  "data": {
    "status": "submitted_for_review",
    "requestId": "185b0aaf-ba79-4f9b-9c6d-5e0587d14314",
    "provider": "rolla",
    "message": "Account details were submitted for review. The deposit account becomes available once approved; retry this endpoint to collect it."
  }
}
```

`requestId` identifies this request — quote it when asking us about a request that is taking longer than you expect.

Call the endpoint again once the account is approved — the response then includes the deposit account with `accountNumber`, `routingNumber` and `swiftCode`. While review is in progress, repeat calls keep returning `202` with the same `requestId`, so retrying is safe and never creates a second request.

<Tip>
  Rather than polling, subscribe to [`account.virtual_account.created`](/api-reference/webhooks/events) — it fires as soon as the deposit account exists, with the account details in the payload.
</Tip>

### Individual USD accounts

Individual accounts have no representatives. When the account was onboarded with
its identity details via [Update Individual Details](/api-reference/endpoint/accounts/update-individual)
(`idDocument`, `address.line2`, and a tax number from `taxId` / `nin` / `bvn`) —
plus the passport/ID from the completed Sumsub KYC — no extra payload is needed:

```json theme={null}
{ "currency": "USD" }
```

You only need `accountData.onboardingDetails` to **override** a field that wasn't
set on the account, e.g.:

```json theme={null}
{
  "currency": "USD",
  "accountData": {
    "onboardingDetails": {
      "idDocument": { "type": "passport", "number": "P123456789", "issueDate": "2020-01-01", "expirationDate": "2030-01-01" }
    }
  }
}
```

## Multiple USD deposit accounts

A client can hold **more than one USD deposit account**, each with its own account number. Pass a `reference` — your own stable id for whatever you need to reconcile separately, such as a storefront, a branch, a marketplace or a channel:

```bash theme={null}
curl -X POST "https://api.rolla.xyz/api/v1/external/accounts/eec3cbed-79d8-4370-87a0-b6be9e287337/bank-accounts" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USD",
    "reference": "store-amazon-uk",
    "label": "Amazon UK"
  }'
```

```json theme={null}
{
  "success": true,
  "message": "Bank account issued successfully",
  "data": {
    "status": "created",
    "bankAccount": {
      "id": "c81f0a44-2d6e-4a5b-9c31-77b0e2d1f904",
      "currency": "USD",
      "accountNumber": "8823410077",
      "accountName": "Beta Logistics LLC",
      "routingNumber": "021000021",
      "reference": "store-amazon-uk",
      "label": "Amazon UK",
      "type": "static",
      "status": "active"
    }
  }
}
```

Only the **account number** differs — bank name, routing number and account holder are shared, because all of these accounts belong to one client at the same bank. Each is still a real account with its own number, not a label over the primary. That account number is what separates the money: funds sent to it are credited against that reference, so you can tell which source a deposit came from without reconciling by amount or sender name. `reference` and `label` are echoed back on [List Bank Accounts](/api-reference/endpoint/accounts/list-bank-accounts) and [Get Funding Instructions](/api-reference/endpoint/accounts/funding-instructions), so you can hand the right details to the right source without keeping your own mapping.

The client's **primary** account is the one issued without a `reference`, and it reports `reference: null`.

That field is the reliable way to tell the two apart, and it is returned on [List Bank Accounts](/api-reference/endpoint/accounts/list-bank-accounts) and [Get Funding Instructions](/api-reference/endpoint/accounts/funding-instructions) as well as at creation — so you can classify an account at any time rather than having to remember the order you created things in. A client can only ever hold one account with `reference: null`: a second call without a `reference` returns the existing account rather than opening another.

<Info>
  **The reference is the idempotency key.** The same reference always returns the same account with `status: already_exists`, so a retry, a duplicated job or a replayed request can never mint a second account for one source. There is no cap on how many references a client can have.
</Info>

<Warning>
  Requesting an additional account requires the client to already hold its primary account — that is what proves the provider approved the entity. Issue one **without** a `reference` first, or the call is rejected.

  Additional accounts are **USD only**. A `reference` with `currency: "NGN"` returns `400`; for per-customer NGN accounts use the customer virtual account endpoint instead. Not every USD provider can issue more than one account per client — where it can't, the call returns `400` explaining so.
</Warning>

## Key Behaviours

<Info>
  Entries in `accountData.representatives` are merged into the prefilled representative with the same `relatedPersonId`, so you only send the fields you're adding — names, addresses and ownership come from the application. Individual accounts use `accountData.onboardingDetails` instead (see above).
</Info>

<Warning>
  The account's application must be **approved** before bank accounts can be issued. If provider-required details are missing, the response lists them under `errors` (USD) or `missingFields` (NGN).
</Warning>

<Note>
  **Re-issuing.** A USD call without a `reference` returns the client's existing account with `status: already_exists` (`200`) rather than creating another — safe to retry. A USD call **with** a `reference` behaves the same way per reference. NGN is one account per provider and returns `409 Conflict` once one exists.
</Note>


## OpenAPI

````yaml POST /accounts/{accountId}/bank-accounts
openapi: 3.1.0
info:
  title: Rolla Developer API
  description: >-
    API for programmatic transaction management on the Rolla platform. Monetary
    `amount` fields use the smallest unit of the referenced currency unless an
    endpoint says otherwise (e.g. NGN = kobo, USD = cents, where **100 cents =
    USD 1.00**).
  version: 1.0.0
servers:
  - url: https://api.rolla.xyz/api/v1/external
    description: Production server
security:
  - apiKeyAuth: []
paths:
  /accounts/{accountId}/bank-accounts:
    post:
      summary: Issue Bank Account
      description: >-
        Issues an NGN or USD deposit account for an approved account. The
        provider is chosen automatically based on currency and account type. NGN
        accounts are issued immediately. USD accounts require provider review:
        the first call submits the account's application data to the provider
        and returns `202`; call again after approval to collect the deposit
        account details.
      operationId: issueBankAccount
      parameters:
        - name: accountId
          in: path
          required: true
          description: >-
            Identifier of an account owned by the same user as your API key's
            business
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueBankAccountRequest'
      responses:
        '201':
          description: Bank account issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Bank account issued successfully
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: issued
                      bankAccount:
                        $ref: '#/components/schemas/BankAccount'
        '202':
          description: USD request accepted and awaiting review
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Bank account request accepted
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                          - submitted_for_review
                          - pending_provider_review
                      requestId:
                        type: string
                        format: uuid
                        description: >-
                          Identifies this request. Repeat calls while it is
                          under review return the same value, so retrying never
                          creates a second request.
                        example: 185b0aaf-ba79-4f9b-9c6d-5e0587d14314
                      provider:
                        type: string
                        description: >-
                          Always `rolla`. Which banking partner holds the
                          account is an implementation detail and is not
                          exposed.
                        example: rolla
                      message:
                        type: string
        '400':
          description: >-
            Application not approved, missing details, or provider validation
            failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: A bank account from this provider already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    IssueBankAccountRequest:
      type: object
      required:
        - currency
      properties:
        currency:
          type: string
          enum:
            - NGN
            - USD
        accountData:
          type: object
          description: >-
            Optional overrides merged over the details prefilled from the
            account's application. The shape depends on the provider — call GET
            /accounts/{accountId}/bank-accounts/requirements to see the
            prefilled payload and what's missing. For USD, entries in
            `representatives` merge into the prefilled person matching the same
            `relatedPersonId`.
          additionalProperties: true
        reference:
          type: string
          minLength: 1
          maxLength: 100
          description: >-
            Your own stable id for whatever you need to reconcile separately — a
            storefront, a branch, a marketplace, a channel. Supplying it issues
            an **additional** deposit account under the same client, with its
            own account number, so incoming funds are attributable to that one
            reference. Omit it for the client's primary account.


            Idempotent: the same reference always returns the same account, so a
            retry cannot create a duplicate. USD only, and only on providers
            that support more than one account per client.
          example: store-amazon-uk
        label:
          type: string
          minLength: 1
          maxLength: 120
          description: >-
            Human-readable name stored alongside the account and echoed back on
            reads, e.g. "Amazon UK".
          example: Amazon UK
    BankAccount:
      type: object
      properties:
        id:
          type: string
          format: uuid
        currency:
          type: string
          enum:
            - NGN
            - USD
        bankName:
          type: string
          example: Guaranty Trust Bank
        accountNumber:
          type: string
          example: '1238726395'
        accountName:
          type: string
          example: Beta Logistics LLC
        routingNumber:
          type: string
          nullable: true
          description: USD accounts only
        swiftCode:
          type: string
          nullable: true
          description: USD accounts only
        bankAddress:
          type: string
          nullable: true
        type:
          type: string
          enum:
            - static
            - dynamic
        status:
          type: string
          example: active
        expiresAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        reference:
          type: string
          nullable: true
          description: >-
            The reference this account was issued under, or `null` on the
            client's primary account. Lets you tell a client's accounts apart
            without keeping your own mapping.
          example: store-amazon-uk
        label:
          type: string
          nullable: true
          description: The label this account was issued with, if any.
          example: Amazon UK
    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your Rolla API key

````