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

# Onboard a bank-transfer user

> Exchanges the identity provider's one-time code, then either runs the bank provider's sequence (account, shared verification, terms, wallet link) or, for every first-time user, answers `consent_required` with a session id for the hosted consent step.

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


## OpenAPI

````yaml https://backend.rheon.io/openapi.json post /v1/fiat/onboard
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/fiat/onboard:
    post:
      tags:
        - Bank transfers
      summary: Onboard a bank-transfer user
      description: >-
        Exchanges the identity provider's one-time code, then either runs the
        bank provider's sequence (account, shared verification, terms, wallet
        link) or, for every first-time user, answers `consent_required` with a
        session id for the hosted consent step.
      operationId: fiatOnboard
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FiatOnboardRequest'
      responses:
        '200':
          description: Onboarded, or consent still needed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FiatOnboardResponse'
        '400':
          description: >-
            Cannot onboard this.


            - `invalid_request`: authCode missing, applicantInfo.nationality not
            alpha-2, applicantInfo.phone not E.164, an individual field empty,
            individual.country not alpha-2, wallet not an EVM address.

            - `<provider code>`: The bank provider refused (4xx): its own code
            is passed through (e.g. `err_validation`), with `fields` naming what
            it rejected when it did.
          x-error-codes:
            - invalid_request
            - <provider code>
          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 permission.

            - `permission_denied`: The key does not carry the `bank` permission.
          x-error-codes:
            - permission_denied
          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'
        '502':
          description: >-
            The provider behind this route is unavailable.


            - `upstream_error`: The payment provider answered a 5xx or did not
            answer.
          x-error-codes:
            - upstream_error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  schemas:
    FiatOnboardRequest:
      type: object
      properties:
        authCode:
          type: string
          minLength: 1
          description: >-
            One-time authorization code from the identity provider's button in
            your UI.
        applicantInfo:
          $ref: '#/components/schemas/ApplicantInfo'
        wallet:
          $ref: '#/components/schemas/EvmAddress'
          description: The user's EVM wallet - the non-custodial USDC destination.
        individual:
          $ref: '#/components/schemas/IndividualInfo'
      required:
        - authCode
        - applicantInfo
        - wallet
        - individual
    FiatOnboardResponse:
      anyOf:
        - $ref: '#/components/schemas/FiatOnboarded'
        - $ref: '#/components/schemas/FiatConsentRequired'
    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.
    ApplicantInfo:
      type: object
      properties:
        nationality:
          $ref: '#/components/schemas/IsoCountry'
          description: ISO 3166-1 alpha-2 nationality (sent upstream upper-cased).
          example: US
        phone:
          type: string
          pattern: ^\+[1-9]\d{6,14}$
          description: E.164 phone number.
          example: '+14155551234'
      required:
        - nationality
        - phone
      description: >-
        The two fields the identity provider does not share and the bank
        provider requires.
    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'
    IndividualInfo:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          description: Legal name of the account holder.
          example: Jane Doe
        email:
          type: string
          minLength: 1
          description: Contact email. Checked for presence only.
          example: jane@example.com
        country:
          $ref: '#/components/schemas/IsoCountry'
          description: ISO 3166-1 alpha-2 country of residence (sent upstream upper-cased).
        category:
          type: string
          minLength: 1
          description: Account category from the provider's individual list.
          example: self_employed
      required:
        - name
        - email
        - country
        - category
    FiatOnboarded:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        status:
          type: string
          const: onboarded
        accountId:
          type: string
          description: The user's account id at the bank provider.
        walletId:
          type: string
          description: The linked wallet id (the settlement destination).
        kycStatus:
          type: string
          description: KYC status right after creation.
          example: pending
      required:
        - environment
        - status
        - accountId
        - walletId
        - kycStatus
      additionalProperties: false
    FiatConsentRequired:
      type: object
      properties:
        environment:
          $ref: '#/components/schemas/Environment'
        status:
          type: string
          const: consent_required
        consentSessionId:
          type: string
          description: >-
            Opaque session id for the hosted consent step. Use it on
            /v1/fiat/consent/link, /status and /complete. Lives in memory for a
            bounded time.
      required:
        - environment
        - status
        - consentSessionId
      additionalProperties: false
      description: >-
        The normal first-time path: the user must consent, on the identity
        provider's hosted page, to sharing their verification with the bank
        provider.
    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
    IsoCountry:
      type: string
      pattern: ^[A-Za-z]{2}$
      description: >-
        ISO 3166-1 alpha-2 country code, two letters (case-insensitive on input;
        sent upstream upper-cased).
      example: DE
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Your API key, issued by us and shown once at creation.

````