# Customers (https://dev.cosmospay.lat/docs/api/customers/)

> CRUD for customers and their payment stats.

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

## List the consumer's customers (with payment stats)

`GET /v1/customers`

Newest first, as `{ data, total, take, skip }` with `take` up to 100 (default 100). Each customer carries `payments`, `succeeded` and the gross settled `total` (amounts added as numbers, whatever the asset) of the payment intents paid from its wallets, on the key's network only. The payer of a settled payment whose wallet no customer holds is added automatically (`reference: auto`).

Requires the `customers:read` scope.

**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` (`CustomerListEntity`): OK
  - `data` (CustomerWithStatsEntity[], required): The customers on this page, newest first.
  - `total` (number, required): Total matching rows, not the page length.
  - `take` (number, required): Page size applied (1 to 100).
  - `skip` (number, required): Number of customers skipped before this page.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)

## Create a customer

`POST /v1/customers`

A customer holds up to 20 `wallets` on Stellar, Solana or Monad (the deprecated `account` counts as one of them), and a wallet named twice counts once. Payments settled from any of them count toward the customer. A wallet another of your customers already holds is `409 customer_wallet_taken`.

Requires the `customers:write` scope.

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

- `name` (string, required): Customer name (1 to 120 characters).
- `alias` (string, optional): An alternative display name (at most 120 characters).
- `note` (string, optional): Free-text note about the customer (at most 500 characters).
- `email` (string, optional): Contact email address (at most 160 characters).
- `account` (string, optional): Deprecated: use `wallets`. One wallet, its chain read from its shape: a Stellar account (G..., checksum verified), a Solana address (base58, 32 bytes) or a Monad address (0x + 40 hex, any case; stored EIP-55 checksummed). It is added to `wallets`.
- `wallets` (CustomerWalletDto[], optional): The customer's wallets, on any chain payments settle on, up to 20. Payment intents paid from any of them count toward the customer. A wallet belongs to one customer: one that another customer of yours already holds answers `409 customer_wallet_taken`.
- `reference` (string, optional): Your own reference for the customer (at most 120 characters).

**Responses**

- `201` (`CustomerEntity`): Created
  - `id` (string, required): Customer id.
  - `name` (string, required): Customer name.
  - `alias` (object, optional): Alternative display name; null when none was given.
  - `email` (object, optional): Contact email address; null when none was given.
  - `wallets` (CustomerWalletEntity[], required): The customer's wallets, oldest first, on any chain payments settle on.
  - `account` (object, optional): Deprecated: read `wallets`. The first wallet's address, kept for clients written when a customer held one account; null without wallets.
  - `note` (object, optional): Free-text note about the customer; null when none was given.
  - `reference` (object, optional): Your own reference for the customer; `auto` for a payer added automatically from a settled payment; null when none.
  - `createdAt` (string (date-time), required): When the customer was created (ISO 8601).
  - `updatedAt` (string (date-time), required): When the customer last changed (ISO 8601).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `409` (`ApiErrorBodyEntity`): `customer_wallet_taken`: Another of your customers already holds that wallet
  - `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.
- `500`: InternalError (see Shared responses)

## Get a customer by id

`GET /v1/customers/{id}`

The customer with its wallets, oldest first, and `account`, the deprecated first wallet's address. Payment stats are on the list only.

Requires the `customers:read` scope.

**Path parameters**

- `id` (string, required)

**Responses**

- `200` (`CustomerEntity`): 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)

## Update a customer

`PATCH /v1/customers/{id}`

Changes only the fields sent (`name`, `alias`, `note`, `email`, `reference`). The deprecated `account` edits the first wallet only: an address replaces or adds it, `null` removes it; other wallets change through `/v1/customers/{id}/wallets`. An address another of your customers holds is `409 customer_wallet_taken`.

Requires the `customers:write` scope.

**Path parameters**

- `id` (string, required)

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

- `name` (string, optional): Customer name (1 to 120 characters).
- `alias` (string, optional): An alternative display name (at most 120 characters).
- `note` (string, optional): Free-text note about the customer (at most 500 characters).
- `email` (string, optional): Contact email address (at most 160 characters).
- `reference` (string, optional): Your own reference for the customer (at most 120 characters).
- `account` (object, optional): Deprecated: manage wallets with `POST` and `DELETE /v1/customers/{id}/wallets`. The customer's first wallet: an address replaces it (or adds one), its chain read from its shape; null removes it.

**Responses**

- `200` (`CustomerEntity`): OK
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `409` (`ApiErrorBodyEntity`): `customer_wallet_taken`: Another of your customers already holds that wallet
- `500`: InternalError (see Shared responses)

## Delete a customer

`DELETE /v1/customers/{id}`

Deletes the customer and its wallets. Payment intents are kept; a later settled payment from one of those wallets adds its payer back as an automatic customer.

Requires the `customers:write` scope.

**Path parameters**

- `id` (string, required)

**Responses**

- `200` (`CustomerDeletedEntity`): OK
  - `id` (string, required): Id of the deleted customer.
  - `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)

## Add a wallet to a customer

`POST /v1/customers/{id}/wallets`

A customer holds any number of wallets (up to 20), on Stellar, Solana or Monad; payments paid from any of them count toward it. A wallet the customer already holds answers as it is.

Requires the `customers:write` scope.

**Path parameters**

- `id` (string, required)

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

- `chain` (string, required): The chain the wallet is on. One of: `stellar`, `solana`, `monad`.
- `address` (string, required): The address on that chain: a Stellar account (G..., checksum verified), a Solana address (base58, 32 bytes) or a Monad address (0x + 40 hex, any case; stored EIP-55 checksummed). Payment intents whose payer (`source`) is this address count toward the customer.
- `label` (string, optional): A name for the wallet, shown beside the address.

**Responses**

- `201` (`CustomerWalletEntity`): Created
  - `id` (string, required): Wallet id, used to remove it from the customer.
  - `chain` (string, required): The chain the wallet is on. One of: `stellar`, `solana`, `monad`.
  - `address` (string, required): The address, in its chain's stored spelling (EIP-55 on Monad).
  - `label` (object, optional): A name for the wallet, shown beside the address; null when none was given.
  - `createdAt` (string (date-time), required): When the wallet was added (ISO 8601).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `409` (`ApiErrorBodyEntity`): `customer_wallet_taken`: Another of your customers already holds that wallet
- `500`: InternalError (see Shared responses)

## Remove a wallet from a customer

`DELETE /v1/customers/{id}/wallets/{walletId}`

Payments from that address stop counting toward this customer, and a later settled payment from it adds it as a new automatic customer. A `walletId` that is not one of this customer's wallets is 404.

Requires the `customers:write` scope.

**Path parameters**

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

**Responses**

- `200` (`CustomerWalletDeletedEntity`): OK
  - `id` (string, required): Id of the removed wallet.
  - `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)

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