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

# Create a Deposit Address

> Create a Deposit Address. Requires deposit addresses to be enabled for your
project and the customer to be provisioned with the banking provider. The
address starts `pending` and becomes `active` once the inbound on-chain
address is assigned.

A destination the project is not permitted to deliver to is rejected with
`403 destinationRailNotAllowed` before any address is provisioned: the
project's outgoing-rail allow-list denies the bank rail, or the routes
table has no permitted/enabled route for the requested crypto network. The
error body carries `details: { network }` — the denied bank rail (e.g.
`wire`) or crypto network (e.g. `polygon`). (Not modeled as a typed error
arm here so the other 403 codes on this operation — e.g.
`vendorConfigMissing`, `ereborConfigMissing` — keep their shape.)



## OpenAPI

````yaml /api-reference/openapi.yaml post /deposit-addresses
openapi: 3.0.3
info:
  title: Polygon OMS Public API
  version: v26.05.28-0001
  description: >-
    Unified API for moving money between crypto and fiat. Three ways to move
    money: Transactions (instant, wallet or card funded, including Cash-In for
    in-person cash deposits), Deposit Addresses (reusable crypto deposit
    configurations), and Virtual Accounts (dedicated bank accounts that
    auto-convert fiat to crypto). Standard transactions follow a two-step flow:
    create a Quote (pricing), then create a Transaction (execution). Cash-in
    codes generate a one-time deposit code for in-person cash deposits at retail
    locations.
  contact:
    name: Polygon OMS
    url: https://oms.polygon.technology
servers:
  - url: https://sandbox-api.polygon.technology/v0.13
    description: Sandbox
  - url: https://api.polygon.technology/v0.13
    description: Production
security:
  - BearerAuth: []
tags:
  - name: Auth
    description: Authentication via FrontEgg
  - name: Customers
    description: Customer management and KYC
  - name: Customer
  - name: Wallet
  - name: Quote
  - name: Transaction
  - name: CashIns
  - name: CashLocation
  - name: Sandbox
  - name: VirtualAccount
  - name: Counterparty
  - name: ExternalAccount
  - name: Reference
  - name: DepositAddress
paths:
  /deposit-addresses:
    post:
      tags:
        - DepositAddress
      summary: Create a Deposit Address
      description: >-
        Create a Deposit Address. Requires deposit addresses to be enabled for
        your

        project and the customer to be provisioned with the banking provider.
        The

        address starts `pending` and becomes `active` once the inbound on-chain

        address is assigned.


        A destination the project is not permitted to deliver to is rejected
        with

        `403 destinationRailNotAllowed` before any address is provisioned: the

        project's outgoing-rail allow-list denies the bank rail, or the routes

        table has no permitted/enabled route for the requested crypto network.
        The

        error body carries `details: { network }` — the denied bank rail (e.g.

        `wire`) or crypto network (e.g. `polygon`). (Not modeled as a typed
        error

        arm here so the other 403 codes on this operation — e.g.

        `vendorConfigMissing`, `ereborConfigMissing` — keep their shape.)
      operationId: createDepositAddress
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositAddressCreateRequest'
      responses:
        '201':
          description: >-
            The request has succeeded and a new resource has been created as a
            result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepositAddress'
              example:
                id: da_jts3wrvsp6dz04fntj1v4g3hyy
                object: depositAddress
                customerId: cst_vfa0nxw6zvyws9g237jrxn4y7k
                status: pending
                expectedSourceAsset: usdc
                expectedSourceNetwork: ethereum
                destination:
                  party:
                    relationship: customer
                    id: cst_vfa0nxw6zvyws9g237jrxn4y7k
                    name: Jane Smith
                  type: bankUs
                  category: fiatAccount
                  id: ext_cc35pa5vgrq1tpsqm4bxy2dkrg
                  asset: usd
                  network: ach
                  displayName: ••••1234
                  payoutOrigin:
                    type: bank
                    id: null
                label: Alice deposit address
                createdAt: '2026-05-14T10:00:00Z'
                updatedAt: '2026-05-14T10:00:00Z'
        '422':
          description: >-
            Returned by the Virtual Account / Deposit Address create when the
            provider terminally rejects the provisioning call. No resource is
            created — provisioning happens before any row is inserted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EreborValidationFailedErrorBody'
components:
  parameters:
    IdempotencyKeyHeader:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Required on POST and PUT requests. Use a unique value per logical
        mutation attempt, for example a UUID.
      schema:
        type: string
  schemas:
    DepositAddressCreateRequest:
      type: object
      required:
        - customerId
        - expectedSourceAsset
        - expectedSourceNetwork
        - destination
      properties:
        customerId:
          type: string
          description: Owning customer (cus_… or legacy public id).
        expectedSourceAsset:
          type: string
          description: >-
            Asset of the inbound crypto the DA expects. "usdc" | "usdt"
            (lowercase).
        expectedSourceNetwork:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: >-
            Network of the inbound crypto the DA expects. `CryptoNetwork` is the
            wire

            vocabulary (ethereum/polygon/base/solana); availability here is
            narrower —

            a closed allowlist of `ethereum` | `base` | `solana`. Any other
            value

            (arbitrum, optimism, polygon, ink, sui, …) is rejected with

            `expectedSourceNetworkUnsupported`.
        destination:
          allOf:
            - $ref: '#/components/schemas/DepositAddressDestinationRequest'
          description: >-
            Side-shaped destination. Fiat: bankUs / bankIban / bankCanada
            registered

            External Account (cryptoToFiatAccount), or walletFiat — the
            customer's own

            Erebor fiat wallet, credited via internal book transfer
            (cryptoToFiatAccount;

            rendered destination network `bookTransfer`). Crypto: walletExternal
            (registered

            ExternalAccount) delivers crypto onward (cryptoToCrypto;
            routes-table gated

            to ethereum/base/solana). walletCrypto is NOT currently supported —
            422

            destinationWalletCryptoNotSupported; use walletExternal. The server
            validates

            details (asset/network/accountHolder) against the resolved
            EA/wallet.
        returnDestination:
          allOf:
            - $ref: '#/components/schemas/CryptoReturnDestination'
          description: >-
            Registered crypto return destination (v0.11-8), for
            operations-triggered

            returns of stranded inbound deposits.
        sponsorGas:
          type: boolean
          description: |-
            When `true`, OMS absorbs the on-chain gas cost for the destination
            delivery. Only `true` is currently supported. Ignored for non-crypto
            destinations (no on-chain leg).
          default: true
        label:
          type: string
          description: Partner display label.
        metadata:
          type: object
          additionalProperties:
            type: string
          description: >-
            Free-form key-value pairs stored on the resource and echoed back on
            reads.
      description: >-
        Create a Deposit Address: the expected inbound asset/network pair plus
        the

        bank destination that receives the converted funds.
      example:
        customerId: cst_vfa0nxw6zvyws9g237jrxn4y7k
        expectedSourceAsset: usdc
        expectedSourceNetwork: ethereum
        destination:
          type: bankUs
          details:
            id: ext_fky491gakzj0dd46qb6whsr2vq
            asset: usd
            network: ach
            accountHolder: customer
        sponsorGas: true
        label: Alice deposit address
    DepositAddress:
      type: object
      properties:
        id:
          allOf:
            - $ref: '#/components/schemas/typeId'
          description: Deposit Address ID (`da_` prefix).
        object:
          type: string
          enum:
            - depositAddress
          description: Resource type discriminator. Always "depositAddress".
        customerId:
          allOf:
            - $ref: '#/components/schemas/typeId'
          description: >-
            Public customer id (cst_...). Named customerId to match VA's naming
            convention.
        customer:
          allOf:
            - $ref: '#/components/schemas/CustomerSummary'
          description: |-
            Resolved id + display name of the owning customer, so list/detail
            rendering needs no follow-up customer fetch (v0.12, #2665).
        status:
          allOf:
            - $ref: '#/components/schemas/DepositAddressStatus'
          description: Current lifecycle status of the deposit address.
        statusReason:
          type: string
          description: Human-readable explanation of the current status.
        expectedSourceAsset:
          type: string
          description: Asset of the inbound crypto the DA expects.
        expectedSourceNetwork:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: Network of the inbound crypto the DA expects.
        depositInstructions:
          allOf:
            - $ref: '#/components/schemas/DepositAddressDepositInstructions'
          description: >-
            Null in the 201 until DEPOSIT_ACCOUNT.OPEN populates the
            Erebor-owned inlet

            address.
        destination:
          allOf:
            - $ref: '#/components/schemas/TransactionDestination'
          description: >-
            V0.10: unified destination shape (payoutOrigin now lives inside
            TransactionDestination).
        returnDestination:
          allOf:
            - $ref: '#/components/schemas/CryptoReturnDestination'
          description: Registered crypto return destination (v0.11-8), echoed when set.
        failureReason:
          allOf:
            - $ref: '#/components/schemas/DepositAddressFailureReason'
          description: >-
            Set when status = `failed`; closed enum identifying the failure
            category.
        sourceToDestination:
          allOf:
            - $ref: '#/components/schemas/SourceToDestination'
          description: |-
            Derived from the destination type: `cryptoToFiatAccount` for a bank
            destination, or `cryptoToCrypto` for a crypto-wallet destination.
        sponsorGas:
          type: boolean
          description: >-
            Whether OMS absorbs the on-chain gas cost for the destination
            delivery.

            Persisted from the create/update request (currently only `true` is

            accepted).
        label:
          type: string
          description: Partner display label.
        metadata:
          type: object
          additionalProperties:
            type: string
          description: Free-form key-value pairs supplied at creation or update.
        createdAt:
          type: string
          format: date-time
          description: When the deposit address was created.
        updatedAt:
          type: string
          format: date-time
          description: When the deposit address was last updated.
      description: >-
        A reusable crypto deposit configuration. Senders deposit the expected

        asset/network to the assigned on-chain address; OMS converts and
        delivers the

        funds to the configured bank destination automatically, creating a

        transaction per inbound deposit.
    EreborValidationFailedErrorBody:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
        code:
          type: string
          enum:
            - ereborValidationFailed
        details:
          $ref: '#/components/schemas/ProviderRejectionDetail'
      description: >-
        Body of the 422 returned when the payment provider terminally rejects a
        Virtual Account or Deposit Address create. `details` carries the
        structured, partner-safe rejection reason when the provider returned a
        decoded error envelope.
    CryptoNetwork:
      type: string
      enum:
        - ethereum
        - polygon
        - base
        - solana
        - tron
      description: >-
        Wire vocabulary for a crypto `network` request field. This is
        vocabulary, not availability: which values a given destination type
        actually accepts is enforced at runtime per destination type, so a value
        legal on the wire may still be refused. Numeric EVM chain ids (for
        example "1", "137", "8453") are not accepted. Values are lowercase on
        the wire; the server additionally normalizes case and surrounding
        whitespace on input, so this schema is stricter than the server in that
        one dimension: send lowercase, untrimmed-safe values to stay strictly
        schema-valid.
    DepositAddressDestinationRequest:
      type: object
      oneOf:
        - $ref: '#/components/schemas/BankUsSideRequest'
        - $ref: '#/components/schemas/BankIbanSideRequest'
        - $ref: '#/components/schemas/BankCanadaSideRequest'
        - $ref: '#/components/schemas/WalletCryptoSideRequest'
        - $ref: '#/components/schemas/WalletExternalRegisteredSideRequest'
        - $ref: '#/components/schemas/WalletFiatDestinationRequest'
      discriminator:
        propertyName: type
        mapping:
          bankUs:
            $ref: '#/components/schemas/BankUsSideRequest'
          bankIban:
            $ref: '#/components/schemas/BankIbanSideRequest'
          bankCanada:
            $ref: '#/components/schemas/BankCanadaSideRequest'
          walletCrypto:
            $ref: '#/components/schemas/WalletCryptoSideRequest'
          walletExternal:
            $ref: '#/components/schemas/WalletExternalRegisteredSideRequest'
          walletFiat:
            $ref: '#/components/schemas/WalletFiatDestinationRequest'
      description: >-
        Deposit Address destination — where converted funds are delivered, on
        the

        shared discriminated side shape.


        Fiat arms (`bankUs` / `bankIban` / `bankCanada`) deliver to a registered

        bank-type External Account (`cryptoToFiatAccount`). The `walletExternal`

        crypto arm delivers crypto onward to a registered wallet
        (`cryptoToCrypto`),

        gated by the routes table to Erebor-serviceable networks

        (ethereum/base/solana — Polygon is rejected). `walletExternal` is

        registered-only (`ext_wlt_` EA id; raw blockchainAddress stays
        Cash-In-only).


        `walletCrypto` is declared but NOT currently supported as a destination:
        it is

        rejected with 422 `destinationWalletCryptoNotSupported`. Use
        `walletExternal`

        for externally-held wallets. (Planned to return for non-custodial
        wallets in

        v0.12.)


        The `walletFiat` arm (v0.12) delivers converted fiat into the customer's
        own

        Erebor fiat wallet via an internal book transfer (`cryptoToFiatAccount`;
        the

        resolved destination instrument reads `network: bookTransfer`). The
        wallet

        must belong to the DA's customer (422 `walletCustomerMismatch`), be
        `active`

        (422 `walletNotActive`), and hold `usd`; the project must have the

        `book_transfer` outgoing rail enabled (403 `destinationRailNotAllowed`).


        The reused side arms carry an optional `amount`, which is meaningless
        for a

        standing destination and rejected at validation. The server validates
        the side

        `details` (asset/network/accountHolder) against the resolved EA/wallet.
    CryptoReturnDestination:
      type: object
      required:
        - type
        - network
        - id
      properties:
        type:
          type: string
          enum:
            - walletCrypto
            - walletExternal
        network:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: >-
            Return network. Must equal the DA's expectedSourceNetwork for the
            return to be usable.
        id:
          type: string
          description: >-
            walletCrypto: OMS wallet id (acc_…). walletExternal: registered
            ExternalAccount id (ext_…).
      description: >-
        Registered crypto return destination for a Deposit Address (v0.11-8):
        where an

        operations-triggered return of a stranded inbound deposit is sent.
        `network` must be one of

        ethereum | base | solana; a custodial (non-multi-asset) walletCrypto
        target is rejected with

        422 returnDestinationMustBeMultiAsset. `network` is a closed
        `CryptoNetwork` enum even

        though this model also appears on response paths
        (`DepositAddress.returnDestination`,

        `RedrivableTransaction.returnDestination`); that is safe only because
        the sole writer is

        gated by `eanetworks.AllServedNetworks()`, a subset of `CryptoNetwork`,
        and that containment

        is enforced by `TestCryptoNetwork_SupersetOfServedNetworks`

        (services/externalaccount/networks/networks_test.go).
    typeId:
      type: string
      pattern: >-
        ^[a-z]+_([0-9a-hjkmnp-tv-z]{26}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$
      description: >-
        Public TypeID, e.g. `txn_01h455vb4pex5vsknk084sn02q`; legacy UUID
        suffixes are accepted until non-v7 rows are retired.
      x-go-type: string
    CustomerSummary:
      type: object
      required:
        - id
        - name
      properties:
        id:
          allOf:
            - $ref: '#/components/schemas/typeId'
          description: The OMS customer id (`cst_` prefix).
        name:
          type: string
          description: Customer display name.
      description: >-
        A minimal, resolved reference to the owning OMS customer — id plus
        display

        name, for list/detail rendering without a follow-up customer fetch

        (v0.12 list enrichment, #2665). Distinct from `Party`: this is always
        the

        record's OWNING customer (Transaction.customerId /
        VirtualAccount.customerId

        / DepositAddress.customerId), not whichever side a party sits on.
    DepositAddressStatus:
      type: string
      enum:
        - pending
        - active
        - frozen
        - closed
        - failed
        - inactiveActionRequired
      description: >-
        Lifecycle of a Deposit Address. pending: awaiting on-chain address

        assignment. active: accepting deposits. frozen: deposits held by
        compliance.

        inactiveActionRequired: destination unusable, re-point `destination` to

        recover. closed: permanently disabled. failed: provisioning failed.
    DepositAddressDepositInstructions:
      type: object
      required:
        - asset
        - network
        - address
      properties:
        asset:
          type: string
          description: Same value as expectedSourceAsset.
        network:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: Same value as expectedSourceNetwork.
        address:
          type: string
          description: Erebor-owned on-chain inlet address for this DA.
        expiresAt:
          type: string
          format: date-time
          description: >-
            Placeholder for a future provider-imposed inlet expiry. Null for
            Erebor

            DDAs today; surfaced now so adding it later is not a breaking
            change.
      description: The on-chain address senders deposit to, with its asset and network.
    TransactionDestination:
      type: object
      oneOf:
        - $ref: '#/components/schemas/WalletCryptoDestination'
        - $ref: '#/components/schemas/WalletExternalDestination'
        - $ref: '#/components/schemas/WalletFiatDestination'
        - $ref: '#/components/schemas/BankUsDestination'
        - $ref: '#/components/schemas/BankIbanDestination'
        - $ref: '#/components/schemas/BankCanadaDestination'
        - $ref: '#/components/schemas/CardDestination'
        - $ref: '#/components/schemas/CashDestination'
      discriminator:
        propertyName: type
        mapping:
          walletCrypto:
            $ref: '#/components/schemas/WalletCryptoDestination'
          walletExternal:
            $ref: '#/components/schemas/WalletExternalDestination'
          walletFiat:
            $ref: '#/components/schemas/WalletFiatDestination'
          bankUs:
            $ref: '#/components/schemas/BankUsDestination'
          bankIban:
            $ref: '#/components/schemas/BankIbanDestination'
          bankCanada:
            $ref: '#/components/schemas/BankCanadaDestination'
          card:
            $ref: '#/components/schemas/CardDestination'
          cash:
            $ref: '#/components/schemas/CashDestination'
      description: >-
        The destination side of a transaction/quote (v0.10): a typed instrument
        plus

        `payoutOrigin`. Amounts live only in `pricing`.
    DepositAddressFailureReason:
      type: string
      enum:
        - provisioningTimeout
        - systemError
        - ereborRejected
        - intlBankAccountCreateRejected
        - noMatchingNetwork
        - blockchainAddressInUse
        - bankAccountInUse
      description: >-
        Closed enum carried on DA when status = "failed". camelCase per partner
        channel naming convention.
    SourceToDestination:
      type: string
      enum:
        - cryptoToCrypto
        - cryptoToCash
        - cryptoToFiatAccount
        - cashToCrypto
        - fiatAccountToCrypto
        - fiatAccountToFiatAccount
      description: >-
        Composite of source and destination instrument categories, inferred

        from each side. The cash corridors

        (`cryptoToCash`, `cashToCrypto`) are derived from a cash-pickup
        destination /

        cash-in source respectively; the rest map straight from the internal
        corridor type.
    ProviderRejectionDetail:
      type: object
      properties:
        provider:
          type: string
          description: Vendor that rejected the request (e.g. `erebor`).
        code:
          type: string
          description: Vendor machine-readable error code (e.g. `INVALID_REQUEST`).
        message:
          type: string
          description: Vendor human-readable message.
        details:
          type: array
          items:
            type: string
          description: 'Per-field validation messages, each in `field: message` form.'
        providerRequestId:
          type: string
          description: Vendor request id, for support escalation.
      description: >-
        Structured, partner-safe description of a terminal provider rejection.
        Populated alongside a `failed` status so the rejection reason is visible
        without support/log access. The same object is returned as the `details`
        field of the `422` error body when a create is terminally rejected by
        the provider.
    BankUsSideRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - bankUs
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/BankUsSideDetails'
        amount:
          $ref: '#/components/schemas/decimalString'
      description: Deliver to a US bank account.
      title: US bank account
    BankIbanSideRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - bankIban
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/BankIbanSideDetails'
        amount:
          $ref: '#/components/schemas/decimalString'
      description: Deliver to an IBAN account over SWIFT (USD).
      title: IBAN bank account
    BankCanadaSideRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - bankCanada
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/BankCanadaSideDetails'
        amount:
          $ref: '#/components/schemas/decimalString'
      description: >-
        Deliver to a Canadian bank account. USD routes over SWIFT; CAD over
        local rails.
      title: Canadian bank account
    WalletCryptoSideRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - walletCrypto
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/WalletCryptoSideDetails'
        amount:
          $ref: '#/components/schemas/decimalString'
      description: Pull from / deliver to an OMS Multi-Chain Wallet owned by the customer.
      title: OMS wallet
    WalletExternalRegisteredSideRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - walletExternal
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/WalletExternalRegisteredSideDetails'
        amount:
          $ref: '#/components/schemas/decimalString'
      description: >-
        Deliver to a wallet held outside OMS, by registered ExternalAccount only

        (id-only; raw addresses are not accepted). Used by Virtual Account

        destinations; the raw-address `WalletExternalSideRequest` stays
        quote/transaction-only.
      title: External wallet (registered)
    WalletFiatDestinationRequest:
      type: object
      required:
        - type
        - details
      properties:
        type:
          type: string
          enum:
            - walletFiat
          description: Type discriminator.
        details:
          $ref: '#/components/schemas/WalletFiatSideDetails'
      description: >-
        Hold the deposited value as a fiat balance in the customer's fiat wallet

        (only `usd` today). Credited by an internal book transfer at the partner

        bank — the resolved destination instrument reads `network:
        bookTransfer`.

        The wallet must belong to the route's customer (422
        `walletCustomerMismatch`)

        and be `active` (422 `walletNotActive`). Request shape per the ratified
        OMS

        v0.12 `WalletFiatDestination` schema: no `network`, no `amount`.
      title: Fiat wallet
    WalletCryptoDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - walletCrypto
          description: Type discriminator.
        category:
          type: string
          enum:
            - crypto
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: >-
            OMS wallet ID (`wlt_` prefix).


            DEVIATION from the ratified OMS v0.12 contract
            (docs/specs/2026-08-05-oms-v0.12-openapi.yaml,

            WalletCryptoInstrument): the ratified shape has `id` required and
            non-nullable. A row

            can classify as this arm (destination_type=walletCrypto/legacy
            walletOms, per #2528)

            before the wallet FK has actually resolved — the "in-flight deposit"
            window

            destInstrumentKind's own doc comment describes — so `id` is nullable
            rather than

            emitting an empty string that fails the `typeId` scalar's pattern.
            Not recorded in

            the upstream snapshot's §4 errata (that section is for defects IN
            the ratified

            snapshot, not BPN's own accepted deviations from it) — flagged here
            and in the PR

            description instead, same class as the
            CardInstrument.id/WalletFiatInstrument.id/

            BankUsInstrument.network deviations elsewhere in this same change.
        asset:
          type: string
          description: Canonical asset identifier.
        network:
          type: string
          description: Network identifier.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: OMS wallet
    WalletExternalDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - walletExternal
          description: Type discriminator.
        category:
          type: string
          enum:
            - crypto
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: External Account ID (`ext_` prefix). Null when externalUnregistered.
        asset:
          type: string
          description: Canonical asset identifier.
        network:
          type: string
          description: Network identifier.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        details:
          type: object
          properties:
            blockchainAddress:
              type: string
              description: On-chain sender address.
          required:
            - blockchainAddress
          nullable: true
          description: >-
            Populated only when `id` is null (unregistered external sender);
            null for registered instruments.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: External wallet
    WalletFiatDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - walletFiat
          description: Type discriminator.
        category:
          type: string
          enum:
            - fiatAccount
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          nullable: true
          description: >-
            OMS fiat wallet ID (`wlt_fiat_` prefix). Look up via `GET /wallets`.

            Plain string, not the `typeId` scalar: `wlt_fiat_` is a two-token

            prefix, which the single-underscore `typeId` pattern cannot
            validate.

            Null on a #2504 book-transfer destination leg's pre-outbound window:

            the wallet FK isn't committed until the outbound leg stamps its

            routing snapshot (a repoint can still change which wallet is
            credited

            until then), so there is genuinely no id to dereference yet — a

            deliberate BPN deviation from the ratified OMS v0.12 contract, which

            types this field required and non-nullable.
        asset:
          type: string
          enum:
            - usd
          description: Fiat currency of the balance. Only `usd` today.
        network:
          type: string
          enum:
            - bookTransfer
          description: |-
            `bookTransfer` when this walletFiat is the destination of a Virtual
            Account / Deposit Address book-transfer credit — an internal ledger
            movement at the partner bank, no external rail. Absent when the
            walletFiat is a funding source.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      description: >-
        Fiat balance wallet destination — the wallet-as-destination projection
        used to

        render an inbound fiat-wallet deposit (design §4.2/§16, #2363).
        RENDERING

        OUTPUT ONLY: this arm exists so a settled deposit into a customer's USD
        fiat

        wallet renders `destination.walletFiat`. It does NOT re-open walletFiat
        as a

        user-SPECIFIABLE input destination — that input deferral (crediting
        VA/DA

        deposits into a wallet, design §16) still holds; only the output shape
        is

        restored here. Over-cut from Task 1 (#2340); returned for Task 10.
      title: Fiat wallet
    BankUsDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - bankUs
          description: Type discriminator.
        category:
          type: string
          enum:
            - fiatAccount
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: >-
            External Account ID (`ext_` prefix). Null when externalUnregistered
            (e.g. an incoming wire from an unregistered sender).
        asset:
          type: string
          enum:
            - usd
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - ach
            - achSameDay
            - wire
          nullable: true
          description: >-
            Network identifier.


            DEVIATION from the ratified OMS v0.12 contract
            (docs/specs/2026-08-05-oms-v0.12-openapi.yaml,

            BankUsInstrument): the ratified shape has `network` required and
            non-nullable

            (no `null` option, unlike `id` on this same schema). Real
            legacy/#2528-era

            contamination means a bankUs row can carry a stored rail (`swift`,

            `international_wire`, `rtp`) that is not one of this enum's three
            members —

            rendering that as the Go zero value (`""`) reverses what #2528/#2666
            were

            built to fix: a real generated client (this repo's own npm/zod
            package

            included) fails `z.enum([...]).parse()` on an empty string just as
            hard as

            on the raw invalid token, rejecting the whole response instead of
            one

            field. Making the field nullable lets a degraded row render
            `network: null`

            — schema-valid for a nullable field, same posture as the
            dropped/absent

            field a pre-#2660 client would have seen. Not recorded in the
            upstream

            snapshot's §4 errata (that section is for defects IN the ratified
            snapshot,

            not BPN's own accepted deviations from it) — flagged here and in the
            PR

            description instead.
        secCode:
          allOf:
            - $ref: '#/components/schemas/SecCode'
          description: >-
            ACH SEC code (populated only on ach/achSameDay). Server-derived from
            the

            destination owner's and the source customer's entityType, read
            directly

            from the DB/Customer record — not from this wire-facing instrument.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        details:
          type: object
          properties:
            accountNumberLast4:
              type: string
              description: Last four digits of the account number.
            bankName:
              type: string
              nullable: true
              description: Bank display name.
          required:
            - accountNumberLast4
            - bankName
          nullable: true
          description: >-
            Populated only when `id` is null (unregistered external sender);
            null for registered instruments.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: US bank account
    BankIbanDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - bankIban
          description: Type discriminator.
        category:
          type: string
          enum:
            - fiatAccount
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: External Account ID (`ext_` prefix). Null when externalUnregistered.
        asset:
          type: string
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - swift
          description: Network identifier.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        details:
          type: object
          properties:
            accountNumberLast4:
              type: string
              description: Last four characters of the IBAN.
            bankName:
              type: string
              nullable: true
              description: Bank display name.
          required:
            - accountNumberLast4
            - bankName
          nullable: true
          description: >-
            Populated only when `id` is null (unregistered external sender);
            null for registered instruments.

            `accountNumberLast4` carries the last 4 of the IBAN — no separate
            `ibanLast4` key on this slim shape.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: IBAN bank account
    BankCanadaDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - bankCanada
          description: Type discriminator.
        category:
          type: string
          enum:
            - fiatAccount
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: External Account ID (`ext_` prefix). Null when externalUnregistered.
        asset:
          type: string
          enum:
            - usd
            - cad
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - swift
            - local
          description: USD uses `swift`; CAD uses `local`.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        details:
          type: object
          properties:
            accountNumberLast4:
              type: string
              description: Last four digits of the account number.
            bankName:
              type: string
              nullable: true
              description: Bank display name.
          required:
            - accountNumberLast4
            - bankName
          nullable: true
          description: >-
            Populated only when `id` is null (unregistered external sender);
            null for registered instruments.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: Canadian bank account
    CardDestination:
      type: object
      required:
        - type
        - category
        - id
        - asset
        - network
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - card
          description: Type discriminator.
        category:
          type: string
          enum:
            - fiatAccount
          description: >-
            High-level grouping: `fiatAccount` for bank or card accounts,
            `crypto` for wallets.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: >-
            Card External Account ID (`ext_` prefix). Null for a
            pre-#1917-migration

            card-sell row, whose external account id column was added nullable

            without a backfill.


            DEVIATION from the ratified OMS v0.12 contract
            (docs/specs/2026-08-05-oms-v0.12-openapi.yaml,

            CardInstrument): the ratified shape has `id` required and
            non-nullable,

            documented as "Always registered". BPN's own historical data
            contradicts

            that — the pre-#1917 legacy rows genuinely have no id — so this
            field is

            deliberately nullable rather than emitting an empty string that
            fails the

            `typeId` scalar's pattern. Not recorded in the upstream snapshot's
            §4

            errata (that section is for defects IN the ratified snapshot, not
            BPN's

            own accepted deviations from it) — flagged here and in the PR
            description

            instead. Follow-up: either backfill the pre-#1917 quote rows so `id`
            is

            always resolvable, or get this nullability formally signed off with
            OMS

            product as a permanent BPN deviation.
        asset:
          type: string
          enum:
            - usd
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - card
          description: Network identifier.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: Card
    CashDestination:
      type: object
      required:
        - type
        - category
        - asset
        - displayName
      properties:
        party:
          allOf:
            - $ref: '#/components/schemas/Party'
          description: Structured identity of who is on this side.
        memo:
          type: string
          description: Free-text message travelling with the transfer on this side's rail.
        type:
          type: string
          enum:
            - cash
          description: Type discriminator.
        category:
          type: string
          enum:
            - cash
          description: 'High-level grouping: always `cash` for cash pickups and drops.'
        asset:
          type: string
          description: Canonical asset identifier.
        cashLocationId:
          type: string
          description: Cash location ID from `GET /cash-locations`.
        displayName:
          type: string
          description: >-
            Opaque, render-only summary for list/detail display. Never a full
            account number/IBAN/PAN.
        payoutOrigin:
          $ref: '#/components/schemas/PayoutOrigin'
        pickupCode:
          type: string
          description: >-
            Retail pickup code for a cash payout. Populated only on cash-out
            (pickup) destinations.
        companyDiscretionaryData:
          type: string
          description: >-
            Echo of the ACH companyDiscretionaryData supplied on the originating
            request.

            Null/absent on non-ACH destinations or when not supplied.
      title: Cash
    BankUsSideDetails:
      type: object
      required:
        - id
        - asset
        - network
        - accountHolder
      properties:
        id:
          type: string
          description: US bank ExternalAccount ID (ext_bankUs_ prefix).
        asset:
          type: string
          enum:
            - usd
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - ach
            - achSameDay
            - wire
          description: Network identifier.
        accountHolder:
          $ref: '#/components/schemas/AccountHolder'
        memo:
          type: string
          allOf:
            - $ref: '#/components/schemas/paymentMemo'
          nullable: true
          description: >-
            Optional customer-supplied payment memo delivered to the
            beneficiary's bank.


            HONORED TODAY ON: a Deposit Address `bankUs` destination with

            `network: "wire"` (create and update). The stored value replaces —
            it does

            not append to — the memo OMS would otherwise generate for every
            outbound

            Fedwire payout from that deposit address. Send `null` (or omit it on
            an

            update that replaces `destination`) to restore the generated memo.
            Because

            the memo is read when each payout wire is built, an edit affects
            only future

            payouts, never one already in flight.


            REJECTED EVERYWHERE ELSE, with `422 memoNotSupported`: this model is
            shared

            by the Quote and Virtual Account request surfaces (create and
            update), and by

            a deposit-address `bankUs` destination on the `ach` / `achSameDay`
            rails. A

            non-empty memo on any of those is refused rather than accepted and
            dropped —

            a silently ignored field on a money path gives the caller a success
            response

            while the payout carries the generated memo. Sending `null`,
            omitting the

            field, or sending only whitespace is always accepted (it means "no
            override").

            Each rejected surface becomes accepting as its slice ships
            (omsx#2254);

            relaxing a 422 into an accepted value never breaks a caller, so no
            client

            needs to change when that happens.


            The error body's `details.reason` tells apart a temporary gap from a

            permanent one: `"notImplemented"` means this surface/rail will
            accept the

            field once its omsx#2254 slice ships (every current rejection above
            is this

            case); `"unsupportedRail"` would mean the field is not a concept on
            this

            rail and never will be (e.g. the shared model also carries fields
            that are

            rail-specific by design, like ACH's `companyDiscretionaryData` on a
            wire

            destination) — retry the first after a release, never the second.
        companyDiscretionaryData:
          type: string
          allOf:
            - $ref: '#/components/schemas/companyDiscretionaryData'
          nullable: true
          description: >-
            Optional ACH-only field for the originator's internal use (NACHA
            batch

            header). See the companyDiscretionaryData scalar doc. Honored only
            when

            network is "ach" or "achSameDay"; 422 memoNotSupported on "wire".
    decimalString:
      type: string
      description: >-
        Wire-safe decimal string for financial float values (USD amounts,
        percentages).
      x-go-type-import:
        path: github.com/shopspring/decimal
      x-go-type: decimal.Decimal
    BankIbanSideDetails:
      type: object
      required:
        - id
        - asset
        - accountHolder
      properties:
        id:
          type: string
          description: IBAN ExternalAccount ID (ext_bankIban_ prefix).
        asset:
          type: string
          enum:
            - usd
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - swift
          description: Network identifier.
          default: swift
        accountHolder:
          $ref: '#/components/schemas/AccountHolder'
        memo:
          type: string
          allOf:
            - $ref: '#/components/schemas/paymentMemo'
          nullable: true
          description: >-
            Optional customer-supplied payment memo delivered via SWIFT
            remittance

            information. See BankUsSideDetails.memo's doc for the full contract
            —

            this is the same field, same charset/length bound, same
            honored-surface

            table, applied to the SWIFT rail instead of Fedwire.
    BankCanadaSideDetails:
      type: object
      required:
        - id
        - asset
        - network
        - accountHolder
      properties:
        id:
          type: string
          description: Canadian bank ExternalAccount ID (ext_bankCa_ prefix).
        asset:
          type: string
          enum:
            - usd
            - cad
          description: Canonical asset identifier.
        network:
          type: string
          enum:
            - swift
            - local
          description: USD uses `swift`; CAD uses `local`. Always explicit.
        accountHolder:
          $ref: '#/components/schemas/AccountHolder'
        memo:
          type: string
          allOf:
            - $ref: '#/components/schemas/paymentMemo'
          nullable: true
          description: >-
            Optional customer-supplied payment memo, honored only on the
            USD/swift

            leg (this model's `local`/CAD leg has no memo concept — Erebor's
            local

            Canadian rail carries no remittance-information field). See

            BankUsSideDetails.memo's doc for the full contract.
    WalletCryptoSideDetails:
      type: object
      required:
        - id
        - asset
        - network
      properties:
        id:
          type: string
          description: OMS wallet ID (wlt_ prefix).
        asset:
          type: string
          description: 'Crypto asset. One of: usdc, usdt.'
        network:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: Network identifier.
    WalletExternalRegisteredSideDetails:
      type: object
      required:
        - id
        - asset
        - network
      properties:
        id:
          type: string
          description: Registered ExternalAccount ID (ext_wlt_ prefix). Required.
        asset:
          type: string
          description: 'Crypto asset. One of: usdc, usdt.'
        network:
          allOf:
            - $ref: '#/components/schemas/CryptoNetwork'
          description: Network identifier.
    WalletFiatSideDetails:
      type: object
      required:
        - id
        - asset
      properties:
        id:
          type: string
          description: OMS fiat wallet ID (wlt_fiat_ prefix).
        asset:
          type: string
          enum:
            - usd
          description: Fiat currency. Only `usd` today.
    Party:
      type: object
      required:
        - relationship
        - id
        - name
      properties:
        relationship:
          type: string
          enum:
            - customer
            - otherCustomer
            - externalRegistered
            - externalUnregistered
          description: Which kind of party this is.
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
          description: >-
            `cst_` for customer/otherCustomer; `ctp_` for externalRegistered;
            null for externalUnregistered.
        name:
          type: string
          nullable: true
          description: >-
            Display name. Null only when genuinely unknown (an unattributed
            external sender).
      description: >-
        Response-only identity of who is on one side of a transaction — a
        uniform

        `{relationship, id, name}` reference. `id` is the Customer (`cst_`) for

        `customer`/`otherCustomer`, the Counterparty (`ctp_`) for

        `externalRegistered`, and null for `externalUnregistered`. Entity type
        and

        address live on the dereferenced Customer/Counterparty resource, not
        here.

        `name` is populated for every relationship except a genuinely-unknown

        external sender.
    PayoutOrigin:
      type: object
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - bank
            - blockchain
        id:
          type: string
          allOf:
            - $ref: '#/components/schemas/typeId'
          nullable: true
      description: |-
        Where last-mile delivery is sent *from*, as a uniform `{type, id}`
        reference — response-only. `id` is the Virtual Account (`va_`) or OMS
        wallet (`wlt_`) funding the leg; null before execution (Quote / Deposit
        Address echo the choice only) or when the origin has no partner-visible
        resource. Rail identifiers for the last-mile transfer live in the
        top-level `tracking` array with `leg: destination` — not here.
    SecCode:
      type: string
      enum:
        - ccd
        - ppd
        - web
      description: >-
        ACH SEC code. Populated only when the destination bank network is

        `ach`/`achSameDay`; null otherwise. Server-derived from the destination
        owner's

        and the source customer's `entityType`.
    AccountHolder:
      type: string
      enum:
        - customer
      description: >-
        Who holds the payout bank account (OMS closed enum). `customer` is the
        only

        valid value.
    paymentMemo:
      type: string
      maxLength: 140
      pattern: ^[A-Za-z0-9 /?:().,'+\-]*$
      description: >-
        Customer-supplied payment memo carried to the beneficiary's bank in the

        ISO 20022 unstructured remittance-information field. Allowed characters
        are

        the ISO 20022 set only: letters, digits, space and `/?:().,'+-`.


        `maxLength` is the ISO 20022 OUTER bound (140), which is what Fedwire
        and

        SWIFT accept. Per-rail rules are enforced server-side rather than in the

        schema. Note that ACH is not simply shorter: the NACHA addenda record is
        a

        different character set (uppercase only, no `/`, but `&=@_#%` allowed)
        capped

        at 80, so the two sets overlap without either containing the other.


        The pattern below MUST stay byte-identical to `iso20022.CharsetPattern`

        (`lib/iso20022/charset.go`), the Go-side authority the request
        validators and

        the outbound memo builders share. `TestCharsetPatternMatchesTypeSpec`
        reads

        this file and fails if the two drift.
      x-go-type: string
    companyDiscretionaryData:
      type: string
      maxLength: 20
      pattern: ^[A-Z0-9 &\-\.\$*\/#@%]*$
      description: |-
        Optional ACH-only field for the originator's internal use, carried
        verbatim in the NACHA batch header (companyDiscretionaryData). Distinct
        from `memo`: this never reaches the beneficiary, it is BPN's own
        origination metadata. Uppercase NACHA character set, max 20 characters.
        Rejected with 422 memoNotSupported on any destination that is not an ACH
        bankUs destination.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Token from POST /auth/token

````