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

# Pay out from a virtual account

> A bank transfer for a user who already holds a virtual account. The account never holds a fiat balance, so this is not a withdrawal: the user's wallet sends stablecoins to the address answered here and the beneficiary is paid in fiat. Poll it on POST /v1/bank/payout/status.

The provider behind this endpoint is currently being connected, docs updating. Until then every answer carries `mock: true`, every money figure is the placeholder `1234567.89`, and ids and shapes are real and deterministic from your input.

<Note>**Provider sandbox on this deployment.** Our backend targets the bank provider's sandbox: bank details are test details, no real transfer settles, and settlement lands on a testnet (read `network` from GET /v1/config). Known limits: only the EUR/IBAN rail is wired for the simulated pay-in, and identity checks run on test-mode applicants. Every response says which environment answered in its `environment` field.</Note>

<Note>**Currently being connected, docs updating.** This endpoint answers `mock: true`: ids, shapes and statuses are real and deterministic from your input, every money figure is the placeholder `1234567.89`, and nothing is sent to a provider. Integrate against the shape; do not compute anything from the figures.</Note>


## OpenAPI

````yaml https://backend.rheon.io/openapi.json post /v1/virtual-accounts/{id}/payouts
openapi: 3.1.0
info:
  title: Rheon partner API
  version: 1.0.0
  description: >-
    The /v1 surface a partner's backend calls with an API key.


    **Authentication.** Every route takes `Authorization: Bearer <key>`. One
    kind of key: it carries its permissions and limits, and works from your
    server, a page, or this playground alike - where it is called from is not
    checked. Treat it as a secret all the same: anyone holding it acts under its
    permissions until it is rotated.


    **Rate limit.** Counted per key, at the requests-per-second rate configured
    on the key - never per IP, so your users do not throttle each other. Over
    it: `429 rate_limited`.


    **Amounts.** Token amounts are decimal strings in the token's smallest unit;
    fiat amounts are decimal strings in major units. Never JSON numbers.


    **Errors.** One envelope everywhere: `{ error: { code, message } }`, with
    `fields` added on bank refusals that named a field. A body that is not valid
    JSON answers `400 invalid_request`; an endpoint that does not exist answers
    `404 not_found`; a deployment with no partner keys configured answers `404
    not_configured` for all of /v1.


    **Environment.** Every response carries `environment` as its first field:
    `sandbox` or `production`, derived from the upstreams this deployment is
    configured against (never a flag). Read it off any response you paste into a
    ticket. In `sandbox` no real money moves and the card corridor is the
    provider's sandbox asset, which differs from production - read it from GET
    /v1/config, not from these docs.


    **Card corridor.** The asset and chain a card purchase settles as are fixed
    per deployment, not chosen per request. GET /v1/config reports the one
    actually configured.
servers:
  - url: https://api.rheon.io
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Configuration
  - name: Crypto deposits
  - name: Orders
  - name: Cards
  - name: Bank transfers
  - name: Virtual accounts
  - name: Reference
  - name: Sandbox only
paths:
  /v1/virtual-accounts/{id}/payouts:
    post:
      tags:
        - Virtual accounts
      summary: Pay out from a virtual account
      description: >-
        A bank transfer for a user who already holds a virtual account. The
        account never holds a fiat balance, so this is not a withdrawal: the
        user's wallet sends stablecoins to the address answered here and the
        beneficiary is paid in fiat. Poll it on POST /v1/bank/payout/status.


        The provider behind this endpoint is currently being connected, docs
        updating. Until then every answer carries `mock: true`, every money
        figure is the placeholder `1234567.89`, and ids and shapes are real and
        deterministic from your input.
      operationId: virtualAccountPayout
      parameters:
        - in: path
          name: id
          schema:
            type: string
            description: The virtual account id.
            example: va_1
          required: true
          description: The virtual account id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VirtualAccountPayoutRequest'
      responses:
        '200':
          description: The payout and where to send.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VirtualAccountPayoutCreated'
        '400':
          description: >-
            Cannot create this.


            - `invalid_request`: Both amounts or neither sent, chain not in the
            catalogue, beneficiary without name or schema, reference over 64
            characters.

            - `no_route`: No payout rail serves that currency.
          x-error-codes:
            - invalid_request
            - no_route
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: >-
            No usable API key.


            - `unauthorized`: Missing, malformed or unknown key. One
            undifferentiated answer on purpose.
          x-error-codes:
            - unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: >-
            Refused by the key's scope.


            - `permission_denied`: The key does not carry the `virtual-accounts`
            permission.

            - `chain_not_allowed`: The key is not enabled for `chain` as a
            source.
          x-error-codes:
            - permission_denied
            - chain_not_allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            Over this key's rate.


            - `rate_limited`: More requests per second than the key is
            configured for. Counted PER KEY (all your users share one bucket),
            never per IP.
          x-error-codes:
            - rate_limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            Our fault.


            - `internal`: Unexpected server error. Detail is in our logs, never
            on the wire.
          x-error-codes:
            - internal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  schemas:
    VirtualAccountPayoutRequest:
      type: object
      properties:
        chain:
          $ref: '#/components/schemas/ChainId'
          type: integer
          exclusiveMinimum: 0
          maximum: 9007199254740991
          description: >-
            Chain id the user's wallet sends the stablecoins from. Must be one
            your key may pay FROM and one the catalogue lists.
          example: 42161
        fiatCurrency:
          $ref: '#/components/schemas/IsoCurrency'
          description: >-
            ISO 4217 code the beneficiary is credited in. It does not have to
            match the currency the virtual account receives.
          example: BRL
        fiatAmount:
          $ref: '#/components/schemas/DecimalMajorUnits'
          description: >-
            What must arrive, decimal string in major units of `fiatCurrency`,
            solved backwards. Send this OR `cryptoAmount`, never both.
        cryptoAmount:
          $ref: '#/components/schemas/DecimalMajorUnits'
          description: >-
            What the user gives up, decimal string in major units of
            `cryptoCurrency`. Send this OR `fiatAmount`, never both.
          example: '1000.00'
        beneficiary:
          $ref: '#/components/schemas/Beneficiary'
        reference:
          description: >-
            Your idempotency key, up to 64 characters. A retry carrying the same
            value answers the original object instead of creating a second one.
          example: payout-2026-09-02-115
          type: string
          maxLength: 64
      required:
        - chain
        - fiatCurrency
        - beneficiary
    VirtualAccountPayoutCreated:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        mock:
          $ref: '#/components/schemas/MockFlag'
        id:
          type: string
          description: >-
            The payout id. Poll it on POST /v1/bank/payout/status - virtual
            account payouts share that endpoint and vocabulary.
          example: bpo_9f0c4e2a18b7d3c6
        depositAddress:
          $ref: '#/components/schemas/EvmAddress'
          description: >-
            Where the user sends the stablecoins. While the payout providers are
            being connected this is the zero address and nothing must be sent to
            it; wired, it is a single-use address derived per payout.
          example: '0x0000000000000000000000000000000000000000'
        depositAmount:
          $ref: '#/components/schemas/MockMoney'
        chain:
          $ref: '#/components/schemas/ChainId'
          description: The chain `depositAddress` lives on - the one the request named.
        fiatAmount:
          $ref: '#/components/schemas/MockMoney'
        rail:
          type: string
          description: >-
            The fastest published payout rail for `fiatCurrency`, as GET
            /v1/limits names it.
          example: pix
        status:
          $ref: '#/components/schemas/PayoutStatus'
      required:
        - environment
        - mock
        - id
        - depositAddress
        - depositAmount
        - chain
        - fiatAmount
        - rail
        - status
      additionalProperties: false
    ErrorEnvelope:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        error:
          type: object
          properties:
            code:
              type: string
              description: >-
                Machine-readable error code. Branch on this, never on the
                message.
              example: invalid_request
            message:
              type: string
              description: Human-readable explanation. Wording may change.
              example: amount must be a decimal string of the token's smallest unit.
            fields:
              description: >-
                Per-field complaints from the bank provider, when it named the
                field it refused (bank routes only). Field names, never values.
              example:
                applicantInfo.nationality: iso3166_1_alpha2
              type: object
              propertyNames:
                type: string
              additionalProperties:
                type: string
          required:
            - code
            - message
          additionalProperties: false
      required:
        - environment
        - error
      additionalProperties: false
      description: The one error shape this API produces.
    ChainId:
      type: integer
      exclusiveMinimum: 0
      maximum: 9007199254740991
      description: EVM chain id, a positive integer (e.g. 42161 for Arbitrum One).
      example: 42161
    IsoCurrency:
      type: string
      pattern: ^[A-Za-z]{3}$
      description: ISO 4217 currency code, three letters (case-insensitive on input).
      example: EUR
    DecimalMajorUnits:
      type: string
      pattern: ^\d+(\.\d+)?$
      description: >-
        Decimal string in the currency's major unit, e.g. "100" or "25.50".
        Never a JSON number.
      example: '100'
    Beneficiary:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          description: The beneficiary's name.
          example: Ana Ribeiro
        schema:
          type: string
          minLength: 1
          description: >-
            Names the shape of the remaining fields, per rail (e.g. `bank_sepa`,
            `bank_uk`, `bank_us`, `bank_swift`, `bank_br_pix`, `bank_mexico`).
            The fields the schema requires are validated when the bank provider
            is wired.
          example: bank_sepa
      required:
        - name
        - schema
      additionalProperties: {}
      description: Always `name` and `schema`; the rest of the fields follow the schema.
    Environment:
      type: string
      enum:
        - sandbox
        - production
      description: >-
        Which environment answered. `sandbox`: at least one money upstream is
        the provider's sandbox - no real money moves there, and the card
        corridor is the sandbox's asset (read GET /v1/config), not the
        documented production one. `production`: every configured upstream is
        real. Derived from the configured upstream hosts at boot, never a flag.
      example: sandbox
    MockFlag:
      type: boolean
      const: true
      description: >-
        Always `true` on this endpoint: the provider behind it is currently
        being connected, docs updating. Ids, shapes and statuses are real and
        deterministic from your input; every money figure is the placeholder
        `1234567.89`; nothing is sent to a provider. The field disappears the
        day the provider is wired, so branch on its presence, not its value.
      example: true
    EvmAddress:
      type: string
      pattern: ^0x[0-9a-fA-F]{40}$
      description: >-
        EVM address: 0x followed by 40 hex characters. Checksum casing is not
        required.
      example: '0x1111111111111111111111111111111111111111'
    MockMoney:
      type: string
      const: '1234567.89'
      description: >-
        Placeholder while the provider is being connected: always `1234567.89`,
        never a price. Do not compute anything from it. When the provider is
        wired this becomes a decimal string in major units.
      example: '1234567.89'
    PayoutStatus:
      type: string
      enum:
        - pending
        - converting
        - sent
        - settled
        - rejected
        - returned
      description: >-
        `pending`: created, waiting for the user's stablecoin at the deposit
        address. `converting`: the stablecoin landed and is being converted.
        `sent`: left the provider, NOT arrived. `settled`: reached the card or
        bank account - the one state to tell the user about. `rejected`: the
        provider refused it. `returned`: sent and came back. While the payout
        providers are being connected every payout answers `pending`.
      example: pending
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Your API key, issued by us and shown once at creation.

````