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

> ## Agent Instructions
> Money is always a decimal string at the currency's precision (for example "29.00"), paired with an ISO 4217 currency field. Never use minor units.
> Build against the sandbox first: base URL https://sandbox-api.bachs.io with sk_sandbox_ keys. Production is https://api.bachs.io with sk_live_ keys; going live is a key swap.
> Treat webhooks (for example collection.succeeded) as the source of truth for fulfilment, never client-side events or redirects.
> Subscriptions are created by completing a checkout for a recurring product. There is no direct create-subscription endpoint.
> IDs carry resource prefixes (cust_, prod_, sub_, chk_, inv_, ref_) and timestamps are ISO 8601 UTC.

# Create a virtual account

> Creates a fixed bank account number in one currency, or returns the virtual account that already exists for that currency. See [Virtual accounts](/connect/virtual-accounts) for capability setup, requirements, deposits, and fees.



## OpenAPI

````yaml /docs/openapi/openapi.json post /v1/virtual-accounts
openapi: 3.0.3
info:
  title: Bachs API
  version: 1.0.0
  description: >-
    The Bachs API lets you accept payments, manage subscriptions, and move money
    across African markets.


    - **Authentication:** pass your secret key as `Authorization: Bearer
    sk_...`. See [Authentication](/authentication) for keys, scopes, and sandbox
    vs production.

    - **Errors:** every error returns a flat `{ "detail", "error_code",
    "doc_url" }`. See [Errors](/errors).

    - **Pagination:** list endpoints return `{ items, pagination }`. See
    [Pagination](/guides/pagination).

    - **Idempotency:** retry `POST` safely with the `Idempotency-Key` header.
    See [Idempotency](/guides/idempotency).

    - **Base URLs and rate limits:** see the
    [Introduction](/api-reference/overview).
  contact:
    name: Bachs Support
    url: https://bachs.io
servers:
  - url: https://api.bachs.io
    description: Production
  - url: https://sandbox-api.bachs.io
    description: Sandbox
security:
  - ApiKeyAuth: []
tags:
  - name: Payments
    description: Accept payments from customers
  - name: Refunds
    description: Create and manage customer refunds
  - name: Disputes
    description: Respond to chargebacks and manage dispute evidence
  - name: Balances
    description: View balances and account information
  - name: Conversions
    description: Quote, execute, and query currency conversions
  - name: Payouts
    description: Withdraw funds to external accounts
  - name: Webhooks
    description: Receive real-time payment notifications
  - name: Authentication
    description: API key management and authentication
  - name: Customers
    description: Create and manage customers
  - name: Products
    description: Define and manage your billing catalog
  - name: Product Groups
    description: Bundle products for multi-plan checkout
  - name: Media
    description: Upload files and get an upload_id to attach to products
  - name: Checkouts
    description: Create and manage API-driven checkouts without products
  - name: Subscriptions
    description: >-
      Recurring billing. Subscriptions are created through checkout; manage them
      here.
  - name: Customer sessions
    description: >-
      Open a hosted portal session so a customer can manage their own
      subscriptions, invoices and cards.
  - name: Accounts
    description: >-
      Create the accounts your platform onboards, request their capabilities,
      walk them through their requirements, and read what they still owe.
  - name: Transfers
    description: Move funds between your platform and the accounts you own.
  - name: Platform Fees
    description: Read the platform's cut of an account's sale.
  - name: Virtual Accounts
    description: Create and read fixed bank account numbers for receiving deposits.
paths:
  /v1/virtual-accounts:
    post:
      tags:
        - Virtual Accounts
      summary: Create a virtual account
      description: >-
        Creates a fixed bank account number in one currency, or returns the
        virtual account that already exists for that currency. See [Virtual
        accounts](/connect/virtual-accounts) for capability setup, requirements,
        deposits, and fees.
      operationId: createVirtualAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVirtualAccountRequest'
            example:
              currency: NGN
      responses:
        '200':
          description: >-
            Virtual account created, or the existing virtual account for the
            requested currency.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VirtualAccountResponse'
              example:
                id: va_8Hs2kQ4mZpXv
                currency: NGN
                account_number: '9902847361'
                bank_name: Example Bank
                bank_code: '000'
                status: active
                created_at: '2026-09-22T09:14:02.000Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/RequirementValidationFailed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - ApiKeyAuth:
            - virtual_accounts:write
components:
  schemas:
    CreateVirtualAccountRequest:
      type: object
      required:
        - currency
      properties:
        currency:
          type: string
          minLength: 3
          maxLength: 3
          description: >-
            Three-letter ISO 4217 code for the money the account accepts, e.g.
            `NGN`. `NGN` is the only currency issued today; any other value is
            rejected with `VIRTUAL_ACCOUNT_CURRENCY_NOT_SUPPORTED`. There is no
            default, because the currency decides what the number can receive.
          example: NGN
    VirtualAccountResponse:
      type: object
      description: >-
        A fixed bank account number belonging to your platform or a connected
        account. It does not expire and can receive deposits at any time.
      properties:
        id:
          type: string
          description: >-
            Unique identifier for the virtual account, prefixed `va_`. It
            appears at `payment_method_details.bank_transfer.virtual_account.id`
            on each payment received through this account.
          example: va_8Hs2kQ4mZpXv
        currency:
          type: string
          description: >-
            Three-letter ISO 4217 code for the money this account accepts, e.g.
            `NGN`. Money sent in any other currency cannot reach it.
          example: NGN
        account_number:
          type: string
          description: >-
            The account number the sender enters in their banking app. Show it
            with `bank_name` so the sender can select the correct destination.
          example: '9902847361'
        bank_name:
          type: string
          description: >-
            The bank that issued the number, as the sender sees it in their
            banking app. Read it from this field rather than storing one name,
            because accounts created later can be issued by a different bank.
          example: Example Bank
        bank_code:
          type: string
          nullable: true
          description: >-
            The issuing bank's code, for apps that select a bank by code rather
            than by name. `null` when the issuing bank gives us none.
          example: '000'
        status:
          type: string
          enum:
            - active
            - inactive
          description: >-
            Whether the virtual account is in use. `active`: the number appears
            in reads and accepts deposits. `inactive`: the number no longer
            appears in reads, but deposits sent to it are still received.
          example: active
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the account number was issued.
          example: '2026-09-22T09:14:02.000Z'
      required:
        - id
        - currency
        - account_number
        - bank_name
        - status
        - created_at
    Error:
      type: object
      description: Standard error response format used across all API endpoints
      properties:
        detail:
          type: string
          description: Human-readable error message explaining what went wrong.
          example: Invalid request parameters
        error_code:
          type: string
          description: >-
            Machine-readable error code. Use this to handle errors
            programmatically. Common codes: VALIDATION_ERROR, UNAUTHORIZED,
            FORBIDDEN, NOT_FOUND, CONFLICT, TOO_MANY_REQUESTS,
            INTERNAL_SERVER_ERROR, BAD_GATEWAY, SERVICE_UNAVAILABLE.
          example: VALIDATION_ERROR
        errors:
          type: array
          description: >-
            Optional array of field-level validation errors. Only present for
            validation errors (400).
          items:
            type: object
            properties:
              field:
                type: string
                description: The field that failed validation.
                example: amount
              message:
                type: string
                description: Description of the validation failure.
                example: Amount must be a positive decimal string
              type:
                type: string
                description: Error type identifier.
                example: value_error
            required:
              - field
              - message
              - type
      required:
        - detail
        - error_code
  responses:
    BadRequest:
      description: >-
        Bad Request - Validation errors or invalid request format. Check the
        `details` object for field-specific validation errors. Common causes:
        missing required fields, invalid data types, values outside allowed
        ranges, or invalid formats.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: Invalid request parameters
            error_code: VALIDATION_ERROR
            errors:
              - field: amount
                message: Amount must be a positive decimal string
                type: value_error
      headers:
        X-RateLimit-Limit:
          description: Maximum number of requests allowed per minute
          schema:
            type: integer
            example: 100
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window
          schema:
            type: integer
            example: 95
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
            example: 1706102400
    Unauthorized:
      description: >-
        Unauthorized - Invalid, missing, or expired API key. Verify your API key
        is correctly formatted and included in the Authorization header as
        `Bearer sk_sandbox_...` or `Bearer sk_live_...`. Check that your key
        hasn't been revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: Invalid API key
            error_code: UNAUTHORIZED
      headers:
        X-RateLimit-Limit:
          description: Maximum number of requests allowed per minute
          schema:
            type: integer
            example: 100
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window
          schema:
            type: integer
            example: 95
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
            example: 1706102400
    Forbidden:
      description: >-
        Forbidden - API key does not have permission for the requested resource.
        Verify you're using the correct account's API key and that you're not
        trying to access another account's data.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: API key does not have permission for this operation
            error_code: FORBIDDEN
      headers:
        X-RateLimit-Limit:
          description: Maximum number of requests allowed per minute
          schema:
            type: integer
            example: 100
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window
          schema:
            type: integer
            example: 95
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
            example: 1706102400
    RequirementValidationFailed:
      description: >-
        One or more submitted fields failed validation. `missing_fields` names
        every field key that has to be corrected or supplied, and
        `missing_documents` names the documents still outstanding. Fix the named
        keys and resubmit; nothing from a rejected submission is persisted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: Submission has missing or invalid required fields
            error_code: VALIDATION_FAILED
            missing_fields:
              - company.registration_number
            missing_documents: []
    TooManyRequests:
      description: >-
        Too Many Requests - Rate limit exceeded. Standard tier allows 100
        requests per minute per API key. Wait a few seconds before retrying.
        Check X-RateLimit-Reset header for when the window resets.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: Rate limit exceeded. Please retry after a few seconds.
            error_code: TOO_MANY_REQUESTS
      headers:
        X-RateLimit-Limit:
          description: Maximum number of requests allowed per minute
          schema:
            type: integer
            example: 100
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window (will be 0)
          schema:
            type: integer
            example: 0
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
            example: 1706102400
        Retry-After:
          description: Number of seconds to wait before retrying
          schema:
            type: integer
            example: 30
    InternalServerError:
      description: >-
        Internal Server Error - An unexpected error occurred while processing
        the request. Retry with exponential backoff. If the issue persists,
        contact support with your request context.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            detail: An unexpected error occurred. Please try again later.
            error_code: INTERNAL_SERVER_ERROR
      headers:
        X-RateLimit-Limit:
          description: Maximum number of requests allowed per minute
          schema:
            type: integer
            example: 100
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window
          schema:
            type: integer
            example: 95
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
            example: 1706102400
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        Bearer token authentication. Pass your API key as `Authorization: Bearer
        sk_...`. See [Authentication](/authentication) for keys, scopes, and
        sandbox vs production.

````