# KYC wallets and bank accounts (https://dev.cosmospay.lat/docs/api/kyc-accounts/)

> A receiver's blockchain wallets and fiat bank accounts, the bank rails and the fields each rail asks for.

Base URL: `https://api.cosmospay.lat/cosmos-api`

## Get the message to sign for the secure wallet flow

`GET /v1/kyc/receivers/{receiverId}/wallets/sign-message`

Fetches from BlindPay the message the customer signs in their own wallet to prove an EOA wallet is theirs. Sign it, then register the wallet with `POST /v1/kyc/receivers/{receiverId}/wallets` and its `signature_tx_hash` instead of an `address`. Nothing is stored.

Requires the `kyc:read` scope.

**Path parameters**

- `receiverId` (string, required)

**Responses**

- `200`: OK
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## List a receiver's wallets

`GET /v1/kyc/receivers/{receiverId}/wallets`

Reads the local mirror, newest first; BlindPay is not called. Paginated with `take` (default 100, max 100) and `skip`; answers `{ data, total, take, skip }`.

Requires the `kyc:read` scope.

**Path parameters**

- `receiverId` (string, required)

**Query parameters**

- `take` (number, optional): Page size: how many items to return (1 to 100).
- `skip` (number, optional): How many items to skip before this page (offset).

**Responses**

- `200` (`WalletListEntity`): OK
  - `data` (WalletEntity[], required): The wallets on this page.
  - `total` (number, required): Matching rows, not the page length.
  - `take` (number, required): The page size applied, from the `take` query parameter (default 100).
  - `skip` (number, required): Rows skipped before this page, from the `skip` query parameter (default 0).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)

## Register a blockchain wallet for a receiver

`POST /v1/kyc/receivers/{receiverId}/wallets`

Registers the wallet at BlindPay under the receiver and mirrors it locally. Send `address` (account abstraction or the non-secure flow), or the `signature_tx_hash` of the message from `GET /v1/kyc/receivers/{receiverId}/wallets/sign-message` (secure EOA flow). `network` is one of BlindPay's: Stellar, Solana, Tron, Ethereum, Base, Arbitrum, Polygon or one of their test networks. A disabled receiver is refused with 403 `account_disabled`.

Requires the `kyc:write` scope.

**Path parameters**

- `receiverId` (string, required)

**Request body** (`application/json`, required): `CreateWalletDto`

- `name` (string, required): A label for the wallet, forwarded to BlindPay.
- `network` (string, required): BlindPay network the wallet is on (mainnets and their test networks). One of: `ethereum`, `base`, `arbitrum`, `polygon`, `stellar`, `solana`, `tron`, `sepolia`, `base_sepolia`, `arbitrum_sepolia`, `polygon_amoy`, `stellar_testnet`, `solana_devnet`.
- `address` (string, optional): Wallet address (account-abstraction / non-secure flow).
- `signature_tx_hash` (string, optional): Signed sign-message tx hash (secure EOA flow).
- `is_account_abstraction` (boolean, optional): Whether `address` is an account-abstraction wallet.

**Responses**

- `201` (`WalletEntity`): Created
  - `id` (string, required): Cosmos Pay id of the wallet; pass it as `blockchain_wallet_id` to onramp quotes and virtual accounts.
  - `blindpayId` (string, required): BlindPay's wallet id (`bw_...`).
  - `name` (object, optional): Label of the wallet.
  - `network` (string, required): BlindPay network the wallet is on (for example `stellar`).
  - `address` (object, optional): Wallet address on `network`.
  - `isAccountAbstraction` (boolean, required): Whether the wallet is an account-abstraction wallet.
  - `createdAt` (string (date-time), required): When this service recorded the wallet (ISO 8601 date-time).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Delete a wallet

`DELETE /v1/kyc/receivers/{receiverId}/wallets/{id}`

Deletes the wallet at BlindPay, then its local row. A wallet id this receiver does not have answers `deleted: true` without calling BlindPay, so repeating a delete succeeds.

Requires the `kyc:write` scope.

**Path parameters**

- `receiverId` (string, required)
- `id` (string, required)

**Responses**

- `200` (`WalletDeletedEntity`): OK
  - `id` (string, required): The wallet id from the request path.
  - `deleted` (boolean, required): Always true.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## List a receiver's bank accounts

`GET /v1/kyc/receivers/{receiverId}/bank-accounts`

Reads the local mirror, newest first; BlindPay is not called, and account numbers are never returned. Paginated with `take` (default 100, max 100) and `skip`; answers `{ data, total, take, skip }`.

Requires the `kyc:read` scope.

**Path parameters**

- `receiverId` (string, required)

**Query parameters**

- `take` (number, optional): Page size: how many items to return (1 to 100).
- `skip` (number, optional): How many items to skip before this page (offset).

**Responses**

- `200` (`BankAccountListEntity`): OK
  - `data` (BankAccountEntity[], required): The bank accounts on this page.
  - `total` (number, required): Matching rows, not the page length.
  - `take` (number, required): The page size applied, from the `take` query parameter (default 100).
  - `skip` (number, required): Rows skipped before this page, from the `skip` query parameter (default 0).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)

## Add a fiat bank account for a receiver

`POST /v1/kyc/receivers/{receiverId}/bank-accounts`

Creates the account at BlindPay under the receiver and mirrors it; offramp payouts settle to it. `type` names the rail, and only that rail's fields are needed (`GET /v1/kyc/bank-details?rail=` lists them). The response carries identifiers only (`rail`, `name`, `country`), never the account numbers. A disabled receiver is refused with 403 `account_disabled`.

Requires the `kyc:write` scope.

**Path parameters**

- `receiverId` (string, required)

**Request body** (`application/json`, required): `CreateBankAccountDto`

- `type` (string, required): The rail the account settles on; it decides which of the rail-specific fields apply. One of: `wire`, `ach`, `rtp`, `pix`, `pix_safe`, `ted`, `spei_bitso`, `transfers_bitso`, `ach_cop_bitso`, `international_swift`, `sepa`.
- `name` (string, required): A label for the account, forwarded to BlindPay.
- `account_class` (string, optional): Ownership class of the account (`individual` or `business`), used by the US and EUR rails. One of: `individual`, `business`.
- `account_type` (string, optional): Checking or savings account, for the rails that distinguish them. One of: `checking`, `saving`.
- `beneficiary_name` (string, optional): Name of the account's beneficiary.
- `recipient_relationship` (string, optional): Relationship to the recipient, as a BlindPay code (for example `first_party`).
- `account_number` (string, optional): Bank account number (`ach`, `wire` and `rtp` rails, United States).
- `routing_number` (string, optional): Bank routing number (`ach`, `wire` and `rtp` rails, United States).
- `business_industry` (string, optional): NAICS code; required for business-class US/SWIFT accounts.
- `phone_number` (string, optional): Phone number with its country code; a compliance field the ACH, Wire, RTP and SWIFT rails may require.
- `tax_id` (string, optional): Tax id; a compliance field the ACH, Wire, RTP and SWIFT rails may require.
- `pix_key` (string, optional): PIX key of the destination account, such as an email address (`pix` rail, Brazil).
- `pix_safe_bank_code` (string, optional): Bank code of the destination account (`pix_safe` rail, Brazil).
- `pix_safe_branch_code` (string, optional): Branch code of the destination account (`pix_safe` rail, Brazil).
- `pix_safe_cpf_cnpj` (string, optional): CPF or CNPJ (Brazilian tax id) of the account holder (`pix_safe` rail, Brazil).
- `ted_bank_code` (string, optional): Bank code of the destination account (`ted` rail, Brazil).
- `ted_branch_code` (string, optional): Branch code of the destination account (`ted` rail, Brazil).
- `ted_cpf_cnpj` (string, optional): CPF or CNPJ (Brazilian tax id) of the account holder (`ted` rail, Brazil).
- `transfers_account` (string, optional): Destination CVU, CBU or alias (`transfers_bitso` rail, Argentina); say which in `transfers_type`.
- `transfers_type` (string, optional): CVU | CBU | ALIAS
- `spei_clabe` (string, optional): CLABE of the destination account (`spei_bitso` rail, Mexico).
- `spei_institution_code` (string, optional): Institution code of the destination bank (`spei_bitso` rail, Mexico).
- `spei_protocol` (string, optional): SPEI protocol value, forwarded to BlindPay (`spei_bitso` rail, Mexico).
- `ach_cop_beneficiary_first_name` (string, optional): Beneficiary first name (`ach_cop_bitso` rail, Colombia).
- `ach_cop_beneficiary_last_name` (string, optional): Beneficiary last name (`ach_cop_bitso` rail, Colombia).
- `ach_cop_document_id` (string, optional): Number of the beneficiary's identity document (`ach_cop_bitso` rail, Colombia).
- `ach_cop_document_type` (string, optional): Type of the beneficiary's identity document (`ach_cop_bitso` rail, Colombia).
- `ach_cop_email` (string, optional): Beneficiary email address (`ach_cop_bitso` rail, Colombia).
- `ach_cop_bank_code` (string, optional): Code of the destination bank (`ach_cop_bitso` rail, Colombia).
- `ach_cop_bank_account` (string, optional): Destination bank account number (`ach_cop_bitso` rail, Colombia).
- `swift_account_holder_name` (string, optional): Name of the account holder (`international_swift` rail).
- `swift_account_number_iban` (string, optional): Account number or IBAN of the destination account (`international_swift` rail).
- `swift_code_bic` (string, optional): SWIFT/BIC code of the destination bank (`international_swift` rail).
- `swift_bank_name` (string, optional): Name of the destination bank (`international_swift` rail).
- `swift_bank_address_line_1` (string, optional): First line of the destination bank's address (`international_swift` rail).
- `swift_bank_city` (string, optional): City of the destination bank (`international_swift` rail).
- `swift_bank_state_province_region` (string, optional): State, province or region of the destination bank (`international_swift` rail).
- `swift_bank_country` (string, optional): Country of the destination bank (`international_swift` rail).
- `swift_bank_postal_code` (string, optional): Postal code of the destination bank (`international_swift` rail).
- `swift_beneficiary_address_line_1` (string, optional): First line of the beneficiary's address (`international_swift` rail).
- `swift_beneficiary_city` (string, optional): City of the beneficiary's address (`international_swift` rail).
- `swift_beneficiary_state_province_region` (string, optional): State, province or region of the beneficiary's address (`international_swift` rail).
- `swift_beneficiary_country` (string, optional): Country of the beneficiary's address (`international_swift` rail).
- `swift_beneficiary_postal_code` (string, optional): Postal code of the beneficiary's address (`international_swift` rail).
- `swift_bank_address_line_2` (string, optional): Second line of the destination bank's address (`international_swift` rail).
- `swift_beneficiary_address_line_2` (string, optional): Second line of the beneficiary's address (`international_swift` rail).
- `swift_payment_code` (string, optional): SWIFT payment/purpose code.
- `swift_intermediary_bank_swift_code_bic` (string, optional): SWIFT/BIC code of the intermediary bank, when the transfer goes through one (`international_swift` rail).
- `swift_intermediary_bank_account_number_iban` (string, optional): Account number or IBAN at the intermediary bank (`international_swift` rail).
- `swift_intermediary_bank_name` (string, optional): Name of the intermediary bank (`international_swift` rail).
- `swift_intermediary_bank_country` (string, optional): Country of the intermediary bank (`international_swift` rail).
- `sepa_iban` (string, optional): IBAN of the destination account (`sepa` rail, EUR).
- `sepa_beneficiary_bic` (string, optional): BIC of the beneficiary's bank (`sepa` rail).
- `sepa_beneficiary_legal_name` (string, optional): Legal name of the beneficiary (`sepa` rail).
- `sepa_beneficiary_address_line_1` (string, optional): First line of the beneficiary's address (`sepa` rail).
- `sepa_beneficiary_city` (string, optional): City of the beneficiary's address (`sepa` rail).
- `sepa_beneficiary_postal_code` (string, optional): Postal code of the beneficiary's address (`sepa` rail).
- `sepa_beneficiary_country` (string, optional): Country of the beneficiary's address (`sepa` rail).
- `sepa_beneficiary_address_line_2` (string, optional): Second line of the beneficiary's address (`sepa` rail).
- `sepa_beneficiary_state_province_region` (string, optional): State, province or region of the beneficiary's address (`sepa` rail).
- `address_line_1` (string, optional): First line of the beneficiary's address, for the US, SWIFT and SEPA rails.
- `address_line_2` (string, optional): Second line of the beneficiary's address, for the US, SWIFT and SEPA rails.
- `city` (string, optional): City of the beneficiary's address, for the US, SWIFT and SEPA rails.
- `state_province_region` (string, optional): State, province or region of the beneficiary's address, for the US, SWIFT and SEPA rails.
- `country` (string, optional): Country of the beneficiary's address, for the US, SWIFT and SEPA rails.
- `postal_code` (string, optional): Postal code of the beneficiary's address, for the US, SWIFT and SEPA rails.

**Responses**

- `201` (`BankAccountEntity`): Created
  - `id` (string, required): Cosmos Pay id of the bank account; pass it as `bank_account_id` to `POST /v1/offramp/quotes`.
  - `blindpayId` (string, required): BlindPay's id for the bank account (`ba_...`).
  - `rail` (object, optional): Rail the account settles on: its BlindPay `type`, such as `ach`.
  - `name` (object, optional): Label of the account, as BlindPay returned it.
  - `country` (object, optional): Country of the account, as BlindPay returned it.
  - `createdAt` (string (date-time), required): When this service recorded the account (ISO 8601 date-time).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Delete a bank account

`DELETE /v1/kyc/receivers/{receiverId}/bank-accounts/{id}`

Deletes the account at BlindPay, then its local row. An id this receiver does not have answers `deleted: true` without calling BlindPay, so repeating a delete succeeds.

Requires the `kyc:write` scope.

**Path parameters**

- `receiverId` (string, required)
- `id` (string, required)

**Responses**

- `200` (`BankAccountDeletedEntity`): OK
  - `id` (string, required): The bank account id from the request path.
  - `deleted` (boolean, required): Always true.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## List available bank rails

`GET /v1/kyc/rails`

Relays BlindPay's catalog of bank rails on the instance of the key's network (production for a mainnet key, development otherwise). Nothing is stored; the body is BlindPay's answer as returned.

Requires the `kyc:read` scope.

**Responses**

- `200`: OK
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Get the field schema required by a rail

`GET /v1/kyc/bank-details`

Relays BlindPay's field schema for the `rail` query parameter (required: 400 `validation_failed` without it), the fields `POST /v1/kyc/receivers/{receiverId}/bank-accounts` needs for that rail. Answered as BlindPay returns it, from the instance of the key's network.

Requires the `kyc:read` scope.

**Query parameters**

- `rail` (string, required): The rail whose fields to list, as a bank account's `type` names it (`GET /v1/kyc/rails` lists the rails). Forwarded to BlindPay as given.

**Responses**

- `200`: OK
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Shared responses

- **ValidationFailed** (`ApiErrorBodyEntity`): The body, query or path failed validation. `message` is an array of the individual failures. Routes that reject a *valid* request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
  - `statusCode` (number, required): Always the HTTP status of the response itself.
  - `code` (string, required): Stable machine-readable code. Branch on this, never on the message: messages are human-facing and may be reworded; codes are never renamed once published. Each response documents the subset it can return. One of: `insufficient_scope`, `no_authenticated_consumer`, `gateway_required`, `admin_console_only`, `not_found`, `validation_failed`, `payload_too_large`, `idempotency_conflict`, `operation_in_flight`, `invalid_state_transition`, `slippage_exceeded`, `insufficient_balance`, `no_path_found`, `trustline_missing`, `invalid_amount`, `invalid_memo`, `transaction_rejected`, `transaction_already_settled`, `recipient_not_registered`, `payment_transfer_mismatch`, `fee_transfer_mismatch`, `merchant_fee_transfer_mismatch`, `payer_mismatch`, `transaction_not_found`, `expired`, `network_unsupported`, `asset_unsupported`, `merchant_fee_unsupported`, `provider_error`, `provider_unavailable`, `quote_not_found`, `quote_expired`, `kyc_state_invalid`, `kyc_review_required`, `account_disabled`, `payload_expired`, `rate_limited`, `customer_wallet_taken`, `alias_taken`, `alias_name_invalid`, `alias_challenge_invalid`, `alias_signature_invalid`, `alias_recovery_invalid`, `alias_address_conflict`, `wallet_handshake_invalid`, `wallet_verifier_invalid`, `wallet_login_code_invalid`, `wallet_login_code_cooldown`, `wallet_session_invalid`, `wallet_signature_invalid`, `wallet_backup_invalid`, `wallet_backup_limit`, `wallet_account_mismatch`, `wallet_provider_unavailable`, `wallet_return_url_not_allowed`, `wallet_recovery_setup_refused`, `easy_recovery_not_found`, `easy_recovery_already_offered`, `easy_recovery_not_offered`, `easy_recovery_code_invalid`, `easy_recovery_burned`, `plugin_not_installed`, `plugin_consent_mismatch`, `plugin_rejected`, `plugin_quota_exceeded`, `plugin_failed`, `protected_payment_buyer_mismatch`, `protected_payment_signature_invalid`, `misconfigured`, `internal_error`.
  - `error` (string, required): The HTTP reason phrase for `statusCode`.
  - `message` (string | string[], required): Human-readable detail. An array when class-validator rejected the body.
  - `details` (object, optional): Machine-readable facts about this failure, present only on the codes whose example shows it: `easy_recovery_code_invalid` carries `{ "attemptsLeft": 4 }`. Absent everywhere else.
  - `path` (string, required): The path of the request that failed.
  - `timestamp` (string, required): When the envelope was produced, ISO 8601.
- **Unauthorized** (`ApiErrorBodyEntity`): `no_authenticated_consumer`: The gateway forwarded no API key identity
- **Forbidden** (`ApiErrorBodyEntity`): `insufficient_scope`: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked `x-cosmos-public-key`, with a message saying so; more scopes will not help, the key is the wrong one. `gateway_required`: the request did not arrive through APISIX.
- **NotFound** (`ApiErrorBodyEntity`): No such resource, or it exists and belongs to another consumer. The two are deliberately the same answer: "exists but not yours" is an ownership oracle.
- **InternalError** (`ApiErrorBodyEntity`): Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
- **UpstreamError** (`ApiErrorBodyEntity`): The provider named in `x-cosmos-upstream` refused the request (`provider_error`) or could not be reached at all (`provider_unavailable`). Nothing was charged; retry is safe with the same `Idempotency-Key`.
- **UpstreamUnavailable** (`ApiErrorBodyEntity`): `provider_unavailable`: the provider in `x-cosmos-upstream` is down or unreachable; the message names it. Retry. `misconfigured`: this deployment is missing configuration the route needs (a provider instance, a fee wallet, the plan header the gateway must forward). Retrying will not help; it is an operator problem.
- **UpstreamTimeout** (`ApiErrorBodyEntity`): The provider in `x-cosmos-upstream` did not answer in time. Whether it acted on the request is unknown. Retry with the same `Idempotency-Key`, never with a fresh one.
