# Invoices (/reference/invoices)

## List invoices

`GET /v1/invoices`

### Parameters

- `limit` (query, optional, integer)
- `cursor` (query, optional, string) — Opaque cursor from the previous page's `next_cursor`.
- `status` (query, optional, string)

### Responses

- `200` — Invoices, newest first.
  - `object` (required, string)
  - `data` (required, array)
  - `has_more` (required, boolean)
  - `next_cursor` (optional, string | null) — Opaque. Pass as `cursor` for the next page. Do not construct or parse it.
  - `data` (optional, array)
    - `object` (required, string)
    - `id` (required, string (uuid))
    - `reference_id` (optional, string | null)
    - `title` (optional, string | null)
    - `price_amount` (required, string)
    - `payer_amount` (optional, string) — What the payer is asked to send (roadmap #2).
    - `payer_fee_cents` (optional, string)
    - `price_currency` (required, string)
    - `status` (required, string)
    - `received_amount` (optional, string)
    - `fee_bps` (optional, integer)
    - `fee_fixed_cents` (optional, string)
    - `fee_cap_cents` (optional, string | null)
    - `expires_at` (optional, string (date-time))
    - `paid_at` (optional, string | null (date-time))
    - `created_at` (optional, string (date-time))
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Create an invoice

`POST /v1/invoices`

Mints one payment address per chain you accept (identical on every EVM chain) and locks a rate per asset for the life of the invoice. The payer sends the quoted atomic amount to the address; the settler credits it once final.

`price_amount` is integer MINOR units of `price_currency` (cents), as a string. Only USD is priced today.

The fee is frozen onto the invoice at creation (`fee_bps`, `fee_fixed_cents`, `fee_cap_cents`) from your plan and is charged to your operational balance when the payment credits — never taken out of the coins.

A `reference_id` makes the create idempotent: a retry returns the FIRST invoice, addresses and rates included.

Live invoices require an active, funded plan (see `GET /v1/billing`); test projects are never gated.

### Parameters

- `Idempotency-Key` (header, optional, string) — Recommended. Replays the stored response for a repeated key.

### Request body (required)

- `price_amount` (required, string) — Minor units (cents), as a string.
- `price_currency` (optional, string)
- `tolerance_bps` (optional, integer) — This invoice's under/overpayment band in basis points of the payer amount. Default 50 (the published ±0.5%), with a one-cent floor.
- `overpayment_policy` (optional, string, one of: keep, refund) — What happens to an overpayment on THIS invoice. Default: your account's setting.
- `fee_to_payer_bps` (optional, integer) — Charge the payer the processing fee: this many basis points of the marked-up amount are added to what the payer sends (Cryptomus `subtract`). Default 0 — the fee comes out of your fee as today.
- `markup_bps` (optional, integer) — A surcharge on the payer: this many basis points of the price, applied before `fee_to_payer_bps` (Cryptomus `discount_percent`, inverted). Default 0.
- `reference_id` (optional, string) — Your order id. Unique per merchant; makes the create idempotent.
- `title` (optional, string)
- `description` (optional, string)
- `customer` (optional, object) — Who is paying, as you know them. Shown and searchable on your console; never shown to the payer.
  - `name` (optional, string)
  - `email` (optional, string (email))
  - `reference_id` (optional, string) — Your own id for this customer.
- `accept` (optional, array) — Restrict this invoice to a subset of the assets you accept.
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (required, string)
- `metadata` (optional, object)
- `return_url` (optional, string (uri))
- `cancel_url` (optional, string (uri))
- `expires_in_seconds` (optional, integer)
- `checkout_mode` (optional, string, one of: hosted, embedded, both) — Which door the payer goes through. `hosted` returns a `checkout_url` to redirect to. `embedded` returns a `client_token` and no URL, for a payment page you build and host yourself. `both` returns each, so your own page has a hosted fallback and a mobile hand-off. Growth plan and up; test keys are never gated.

### Responses

- `200` — An invoice with this `reference_id` already existed; returned unchanged.
  - `object` (required, string)
  - `id` (required, string (uuid)) — Public id. Use it in URLs and webhooks.
  - `reference_id` (optional, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `price_amount` (required, string) — Minor units of `price_currency`, as a string.
  - `payer_amount` (required, string) — What the payer is asked to send — the price carrying this invoice's `markup_bps` and `fee_to_payer_bps`. Equals `price_amount` when the knobs are absent, and is the centre of the settlement band either way.
  - `payer_fee_cents` (required, string) — `payer_amount` − `price_amount`, never negative: the payer-funded slice.
  - `tolerance_bps` (optional, integer | null) — This invoice's under/overpayment band in bps; null = the published 50.
  - `overpayment_policy` (optional, string | null, one of: keep, refund, null) — This invoice's own overpayment disposition; null = your account's default.
  - `fee_to_payer_bps` (optional, integer) — The processing fee charged to the payer, bps.
  - `markup_bps` (optional, integer) — The surcharge on the payer, bps of the price.
  - `price_currency` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `fee_bps` (required, integer)
  - `fee_fixed_cents` (optional, string)
  - `fee_cap_cents` (optional, string | null)
  - `metadata` (optional, object)
  - `customer` (optional, object | null) — Who is paying, as you sent it on create. Null when none was given.
    - `name` (optional, string | null)
    - `email` (optional, string | null)
    - `reference_id` (optional, string | null)
  - `return_url` (optional, string | null)
  - `cancel_url` (optional, string | null)
  - `expires_at` (required, string (date-time))
  - `created_at` (optional, string (date-time))
  - `received_amount` (optional, string) — Cumulative minor units credited so far.
  - `excess_amount` (optional, string) — Minor units received beyond the price, outside the ±0.5% tolerance band. `0` unless the invoice is `overpaid`. Stored rather than derived, so it stays correct after a refund unwinds part of it.
  - `checkout_url` (optional, string | null) — Where to send the payer. Treat it as a bearer credential: the token in it is the entire authorisation for the checkout page, so it is not the same value as `id` and must not be logged where `id` would be. Null when `checkout_mode` is `embedded` — you asked not to be given one. The hosted page still ANSWERS for that token, so a payer who already holds a link never meets a dead page; we simply stop handing you a URL, which means code that redirects by accident fails on your side instead of standing a customer on our brand.
  - `checkout_mode` (optional, string, one of: hosted, embedded, both) — Which door this invoice was created for, frozen at creation. `hosted` redirects the payer to us. `embedded` means you render the payment page yourself. `both` gives you a page AND a hosted URL to fall back to.
  - `client_token` (optional, string | null) — The payer-facing credential for building your own checkout: `ck_` + 43 base64url characters, the whole authorisation for `GET /v1/checkout/{token}`. Null on `hosted`. 🔴 THIS IS THE ONLY CREDENTIAL OF OURS THAT IS SAFE IN A BROWSER. It grants the payer view of ONE invoice — no fee, no metadata, no other order, nothing that reaches this API. Your `mp…` secret key must never reach front-end JavaScript: it carries every invoice and every scope the key holds. Available from the Growth plan up, and on test keys regardless of plan.
  - `paid_in` (optional, object | null) — The one coin the invoice was paid in and the refund arithmetic on it, in ATOMIC units of that coin — unlike `received_amount`, which is minor units of `price_currency`. Null until a payment is credited. `refundable_atomic` is what `POST /v1/invoices/{id}/refund` accepts right now: received, less every refund not known to have failed.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `decimals` (required, integer)
    - `received_atomic` (required, string) — Every credited payment, summed.
    - `refunded_atomic` (required, string) — Refunds pending, sent or confirmed — money spoken for.
    - `refundable_atomic` (required, string) — received − refunded.
  - `addresses` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `address` (required, string)
    - `memo` (optional, string | null)
  - `quotes` (required, array) — Exactly what the payer must send, per (chain, asset), at the locked rate.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `contract` (optional, string | null)
    - `amount_atomic` (required, string) — What the payer must send.
    - `amount` (optional, string) — The same, human-readable. Derived; never compare against it.
    - `decimals` (required, integer)
    - `rate` (required, string) — Units of `price_currency` per whole unit of `asset`.
    - `rate_source` (optional, string)
- `201` — Created.
  - `object` (required, string)
  - `id` (required, string (uuid)) — Public id. Use it in URLs and webhooks.
  - `reference_id` (optional, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `price_amount` (required, string) — Minor units of `price_currency`, as a string.
  - `payer_amount` (required, string) — What the payer is asked to send — the price carrying this invoice's `markup_bps` and `fee_to_payer_bps`. Equals `price_amount` when the knobs are absent, and is the centre of the settlement band either way.
  - `payer_fee_cents` (required, string) — `payer_amount` − `price_amount`, never negative: the payer-funded slice.
  - `tolerance_bps` (optional, integer | null) — This invoice's under/overpayment band in bps; null = the published 50.
  - `overpayment_policy` (optional, string | null, one of: keep, refund, null) — This invoice's own overpayment disposition; null = your account's default.
  - `fee_to_payer_bps` (optional, integer) — The processing fee charged to the payer, bps.
  - `markup_bps` (optional, integer) — The surcharge on the payer, bps of the price.
  - `price_currency` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `fee_bps` (required, integer)
  - `fee_fixed_cents` (optional, string)
  - `fee_cap_cents` (optional, string | null)
  - `metadata` (optional, object)
  - `customer` (optional, object | null) — Who is paying, as you sent it on create. Null when none was given.
    - `name` (optional, string | null)
    - `email` (optional, string | null)
    - `reference_id` (optional, string | null)
  - `return_url` (optional, string | null)
  - `cancel_url` (optional, string | null)
  - `expires_at` (required, string (date-time))
  - `created_at` (optional, string (date-time))
  - `received_amount` (optional, string) — Cumulative minor units credited so far.
  - `excess_amount` (optional, string) — Minor units received beyond the price, outside the ±0.5% tolerance band. `0` unless the invoice is `overpaid`. Stored rather than derived, so it stays correct after a refund unwinds part of it.
  - `checkout_url` (optional, string | null) — Where to send the payer. Treat it as a bearer credential: the token in it is the entire authorisation for the checkout page, so it is not the same value as `id` and must not be logged where `id` would be. Null when `checkout_mode` is `embedded` — you asked not to be given one. The hosted page still ANSWERS for that token, so a payer who already holds a link never meets a dead page; we simply stop handing you a URL, which means code that redirects by accident fails on your side instead of standing a customer on our brand.
  - `checkout_mode` (optional, string, one of: hosted, embedded, both) — Which door this invoice was created for, frozen at creation. `hosted` redirects the payer to us. `embedded` means you render the payment page yourself. `both` gives you a page AND a hosted URL to fall back to.
  - `client_token` (optional, string | null) — The payer-facing credential for building your own checkout: `ck_` + 43 base64url characters, the whole authorisation for `GET /v1/checkout/{token}`. Null on `hosted`. 🔴 THIS IS THE ONLY CREDENTIAL OF OURS THAT IS SAFE IN A BROWSER. It grants the payer view of ONE invoice — no fee, no metadata, no other order, nothing that reaches this API. Your `mp…` secret key must never reach front-end JavaScript: it carries every invoice and every scope the key holds. Available from the Growth plan up, and on test keys regardless of plan.
  - `paid_in` (optional, object | null) — The one coin the invoice was paid in and the refund arithmetic on it, in ATOMIC units of that coin — unlike `received_amount`, which is minor units of `price_currency`. Null until a payment is credited. `refundable_atomic` is what `POST /v1/invoices/{id}/refund` accepts right now: received, less every refund not known to have failed.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `decimals` (required, integer)
    - `received_atomic` (required, string) — Every credited payment, summed.
    - `refunded_atomic` (required, string) — Refunds pending, sent or confirmed — money spoken for.
    - `refundable_atomic` (required, string) — received − refunded.
  - `addresses` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `address` (required, string)
    - `memo` (optional, string | null)
  - `quotes` (required, array) — Exactly what the payer must send, per (chain, asset), at the locked rate.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `contract` (optional, string | null)
    - `amount_atomic` (required, string) — What the payer must send.
    - `amount` (optional, string) — The same, human-readable. Derived; never compare against it.
    - `decimals` (required, integer)
    - `rate` (required, string) — Units of `price_currency` per whole unit of `asset`.
    - `rate_source` (optional, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `merchant_not_found` — this key's project is not a merchant account.
- `422` — `plan_not_active`, `billing_balance_exhausted`, `no_payment_methods`, `currency_not_supported`, `invalid_price`, `embedded_checkout_not_on_plan`. `url_not_on_verified_domain` — on a live project with a verified domain, `return_url` and `cancel_url` must be on it.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Read an invoice with its addresses and locked rates

`GET /v1/invoices/{id}`

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.

### Responses

- `200` — The invoice.
  - `object` (required, string)
  - `id` (required, string (uuid)) — Public id. Use it in URLs and webhooks.
  - `reference_id` (optional, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `price_amount` (required, string) — Minor units of `price_currency`, as a string.
  - `payer_amount` (required, string) — What the payer is asked to send — the price carrying this invoice's `markup_bps` and `fee_to_payer_bps`. Equals `price_amount` when the knobs are absent, and is the centre of the settlement band either way.
  - `payer_fee_cents` (required, string) — `payer_amount` − `price_amount`, never negative: the payer-funded slice.
  - `tolerance_bps` (optional, integer | null) — This invoice's under/overpayment band in bps; null = the published 50.
  - `overpayment_policy` (optional, string | null, one of: keep, refund, null) — This invoice's own overpayment disposition; null = your account's default.
  - `fee_to_payer_bps` (optional, integer) — The processing fee charged to the payer, bps.
  - `markup_bps` (optional, integer) — The surcharge on the payer, bps of the price.
  - `price_currency` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `fee_bps` (required, integer)
  - `fee_fixed_cents` (optional, string)
  - `fee_cap_cents` (optional, string | null)
  - `metadata` (optional, object)
  - `customer` (optional, object | null) — Who is paying, as you sent it on create. Null when none was given.
    - `name` (optional, string | null)
    - `email` (optional, string | null)
    - `reference_id` (optional, string | null)
  - `return_url` (optional, string | null)
  - `cancel_url` (optional, string | null)
  - `expires_at` (required, string (date-time))
  - `created_at` (optional, string (date-time))
  - `received_amount` (optional, string) — Cumulative minor units credited so far.
  - `excess_amount` (optional, string) — Minor units received beyond the price, outside the ±0.5% tolerance band. `0` unless the invoice is `overpaid`. Stored rather than derived, so it stays correct after a refund unwinds part of it.
  - `checkout_url` (optional, string | null) — Where to send the payer. Treat it as a bearer credential: the token in it is the entire authorisation for the checkout page, so it is not the same value as `id` and must not be logged where `id` would be. Null when `checkout_mode` is `embedded` — you asked not to be given one. The hosted page still ANSWERS for that token, so a payer who already holds a link never meets a dead page; we simply stop handing you a URL, which means code that redirects by accident fails on your side instead of standing a customer on our brand.
  - `checkout_mode` (optional, string, one of: hosted, embedded, both) — Which door this invoice was created for, frozen at creation. `hosted` redirects the payer to us. `embedded` means you render the payment page yourself. `both` gives you a page AND a hosted URL to fall back to.
  - `client_token` (optional, string | null) — The payer-facing credential for building your own checkout: `ck_` + 43 base64url characters, the whole authorisation for `GET /v1/checkout/{token}`. Null on `hosted`. 🔴 THIS IS THE ONLY CREDENTIAL OF OURS THAT IS SAFE IN A BROWSER. It grants the payer view of ONE invoice — no fee, no metadata, no other order, nothing that reaches this API. Your `mp…` secret key must never reach front-end JavaScript: it carries every invoice and every scope the key holds. Available from the Growth plan up, and on test keys regardless of plan.
  - `paid_in` (optional, object | null) — The one coin the invoice was paid in and the refund arithmetic on it, in ATOMIC units of that coin — unlike `received_amount`, which is minor units of `price_currency`. Null until a payment is credited. `refundable_atomic` is what `POST /v1/invoices/{id}/refund` accepts right now: received, less every refund not known to have failed.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `decimals` (required, integer)
    - `received_atomic` (required, string) — Every credited payment, summed.
    - `refunded_atomic` (required, string) — Refunds pending, sent or confirmed — money spoken for.
    - `refundable_atomic` (required, string) — received − refunded.
  - `addresses` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `address` (required, string)
    - `memo` (optional, string | null)
  - `quotes` (required, array) — Exactly what the payer must send, per (chain, asset), at the locked rate.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `contract` (optional, string | null)
    - `amount_atomic` (required, string) — What the payer must send.
    - `amount` (optional, string) — The same, human-readable. Derived; never compare against it.
    - `decimals` (required, integer)
    - `rate` (required, string) — Units of `price_currency` per whole unit of `asset`.
    - `rate_source` (optional, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — Not found.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Withdraw an unpaid invoice

`POST /v1/invoices/{id}/cancel`

Closes the window early, for an order that was abandoned or replaced. Needs `invoices:write` rather than a payout scope, because cancelling moves no money.

🔴 IT DOES NOT CLOSE THE PAYMENT ADDRESS. The address is an ordinary keyless customer wallet and stays watched forever, so a transfer arriving afterwards is still detected and still credited — surfaced as `late`, never voided.

An invoice that has already received money cannot be cancelled: reversing a credited payment is a refund, which needs a destination only the payer can give.

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.

### Responses

- `200` — The cancelled invoice.
  - `object` (required, string)
  - `id` (required, string (uuid)) — Public id. Use it in URLs and webhooks.
  - `reference_id` (optional, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `price_amount` (required, string) — Minor units of `price_currency`, as a string.
  - `payer_amount` (required, string) — What the payer is asked to send — the price carrying this invoice's `markup_bps` and `fee_to_payer_bps`. Equals `price_amount` when the knobs are absent, and is the centre of the settlement band either way.
  - `payer_fee_cents` (required, string) — `payer_amount` − `price_amount`, never negative: the payer-funded slice.
  - `tolerance_bps` (optional, integer | null) — This invoice's under/overpayment band in bps; null = the published 50.
  - `overpayment_policy` (optional, string | null, one of: keep, refund, null) — This invoice's own overpayment disposition; null = your account's default.
  - `fee_to_payer_bps` (optional, integer) — The processing fee charged to the payer, bps.
  - `markup_bps` (optional, integer) — The surcharge on the payer, bps of the price.
  - `price_currency` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `fee_bps` (required, integer)
  - `fee_fixed_cents` (optional, string)
  - `fee_cap_cents` (optional, string | null)
  - `metadata` (optional, object)
  - `customer` (optional, object | null) — Who is paying, as you sent it on create. Null when none was given.
    - `name` (optional, string | null)
    - `email` (optional, string | null)
    - `reference_id` (optional, string | null)
  - `return_url` (optional, string | null)
  - `cancel_url` (optional, string | null)
  - `expires_at` (required, string (date-time))
  - `created_at` (optional, string (date-time))
  - `received_amount` (optional, string) — Cumulative minor units credited so far.
  - `excess_amount` (optional, string) — Minor units received beyond the price, outside the ±0.5% tolerance band. `0` unless the invoice is `overpaid`. Stored rather than derived, so it stays correct after a refund unwinds part of it.
  - `checkout_url` (optional, string | null) — Where to send the payer. Treat it as a bearer credential: the token in it is the entire authorisation for the checkout page, so it is not the same value as `id` and must not be logged where `id` would be. Null when `checkout_mode` is `embedded` — you asked not to be given one. The hosted page still ANSWERS for that token, so a payer who already holds a link never meets a dead page; we simply stop handing you a URL, which means code that redirects by accident fails on your side instead of standing a customer on our brand.
  - `checkout_mode` (optional, string, one of: hosted, embedded, both) — Which door this invoice was created for, frozen at creation. `hosted` redirects the payer to us. `embedded` means you render the payment page yourself. `both` gives you a page AND a hosted URL to fall back to.
  - `client_token` (optional, string | null) — The payer-facing credential for building your own checkout: `ck_` + 43 base64url characters, the whole authorisation for `GET /v1/checkout/{token}`. Null on `hosted`. 🔴 THIS IS THE ONLY CREDENTIAL OF OURS THAT IS SAFE IN A BROWSER. It grants the payer view of ONE invoice — no fee, no metadata, no other order, nothing that reaches this API. Your `mp…` secret key must never reach front-end JavaScript: it carries every invoice and every scope the key holds. Available from the Growth plan up, and on test keys regardless of plan.
  - `paid_in` (optional, object | null) — The one coin the invoice was paid in and the refund arithmetic on it, in ATOMIC units of that coin — unlike `received_amount`, which is minor units of `price_currency`. Null until a payment is credited. `refundable_atomic` is what `POST /v1/invoices/{id}/refund` accepts right now: received, less every refund not known to have failed.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `decimals` (required, integer)
    - `received_atomic` (required, string) — Every credited payment, summed.
    - `refunded_atomic` (required, string) — Refunds pending, sent or confirmed — money spoken for.
    - `refundable_atomic` (required, string) — received − refunded.
  - `addresses` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `address` (required, string)
    - `memo` (optional, string | null)
  - `quotes` (required, array) — Exactly what the payer must send, per (chain, asset), at the locked rate.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `contract` (optional, string | null)
    - `amount_atomic` (required, string) — What the payer must send.
    - `amount` (optional, string) — The same, human-readable. Derived; never compare against it.
    - `decimals` (required, integer)
    - `rate` (required, string) — Units of `price_currency` per whole unit of `asset`.
    - `rate_source` (optional, string)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `invoice_not_found`.
- `409` — `invoice_closed` — already cancelled, expired or settled.
- `422` — `invoice_has_payments` — money has already been received against it.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Return some or all of a paid invoice

`POST /v1/invoices/{id}/refund`

Sends `amount` of the coin the invoice was paid in to `to_address`, from the funds that invoice brought in. Omit `amount` to return everything still refundable (`paid_in.refundable_atomic` on the invoice). Partial refunds may be repeated up to that bound; when the whole receipt has been returned the invoice becomes `refunded`.

🔴 **A `payout` key, signing on, `Idempotency-Key` required** — this is money out to an address of your choosing, and the credential in a storefront must never be able to do that. Priced as a payout: your plan's payout fee from the prepaid balance, the network fee as on any payout; above the approval threshold, or past the daily automatic line, the carrying payout waits for MolnPay's review (never refused for the daily total). The payout allowlist does NOT apply — the destination is the payer's, not yours.

`201` is a promise, not a fact: the refund is `sent` while the carrying payout is queued or in flight. `invoice.refunded` fires when it confirms on chain; `invoice.refund_failed` if it does not, and the amount returns to your balance.

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.
- `Idempotency-Key` (header, required, string) — REQUIRED. A retry without one can send funds twice, so the request is rejected outright.

### Request body (required)

- `amount` (optional, string) — Atomic units of `paid_in.asset`, as a STRING. Omit for the whole refundable remainder.
- `decimals` (optional, integer) — Must equal `paid_in.decimals` when `amount` is given — the echo every money field in this API requires.
- `to_address` (required, string) — Where it goes. The payment's sender is a reasonable default ONLY when it was a personal wallet; an exchange's hot wallet credits nobody.
- `reason` (optional, string) — Your note. Shown on your refunds screen, never to the payer.

### Responses

- `201` — The refund, `sent`.
  - `object` (required, string)
  - `id` (required, string (uuid))
  - `invoice_id` (required, string (uuid))
  - `kind` (required, string, one of: overpayment, merchant)
  - `status` (required, string, one of: awaiting_address, pending, sent, confirmed, failed, skipped)
  - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
  - `asset` (required, string)
  - `contract` (optional, string | null)
  - `decimals` (optional, integer | null)
  - `amount_atomic` (required, string) — What is being returned, in atomic units of `asset` — the gross.
  - `amount` (optional, string | null) — `amount_atomic` in whole units. Display only.
  - `fee_atomic` (optional, string) — Network cost taken OUT of the refund (overpayment kind only; `0` on the merchant kind).
  - `net_atomic` (optional, string) — What the destination receives.
  - `to_address` (optional, string | null)
  - `reason` (optional, string | null) — Your note (merchant kind). Never shown to the payer.
  - `payout_id` (optional, string | null (uuid)) — The payout that carries it; read it under `GET /v1/payouts/{id}` with a payout or wallet key.
  - `tx_hash` (optional, string | null)
  - `failure_code` (optional, string | null) — Set with `status: failed` — the carrying payout's failure, rejection or cancellation.
  - `skip_reason` (optional, string | null) — Overpayment kind: why nothing was sent (`uneconomical`, `policy_keep`, …).
  - `network_fee_paid_by` (optional, string, one of: payer, molnpay) — `payer` on the overpayment kind (out of the refund); `molnpay` on the merchant kind, as on every payout — your plan's payout fee applies from the prepaid balance.
  - `created_at` (required, string (date-time))
  - `sent_at` (optional, string | null (date-time)) — When the carrying payout was created.
  - `completed_at` (optional, string | null (date-time)) — When it confirmed on chain, or failed.
- `400` — `invalid_amount` — not an atomic integer string; `invalid_body`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — `wrong_key_kind`, `signing_disabled`.
- `404` — `invoice_not_found`.
- `409` — `not_refundable` — the invoice is not paid, overpaid or late; `nothing_to_refund` — everything received has already been refunded; `idempotency_key_reuse`.
- `422` — `refund_exceeds_received`; `decimals_mismatch`; `insufficient_balance` — the funds this invoice brought in were converted or paid out since and cannot be returned from here; `invalid_address`.
- `429` — `too_many_pending_payouts`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## The checkout document for a page you render yourself

`GET /v1/invoices/{id}/checkout`

The same payer-safe document `GET /v1/checkout/{token}` returns, from the same builder — fetched with your API key instead of the payer's token, so your customer's browser never has to reach us.

🔴 BUILD YOUR PAGE FROM THIS, NEVER FROM `GET /v1/invoices/{id}`. That response's `quotes[]` is the FULL price, always, because that is the figure the rate was locked at. Render it on a part-paid invoice and you tell a payer who has already sent $31 of a $50 order to send $50 more; they overpay by $31, and that needs a refund we may not be able to make. The `options[]` here are the REMAINDER, re-priced at the same locked rate with integer arithmetic that rounds up.

🔴 It stays payer-safe, so you may relay it to a browser verbatim: no fee in any form, no `metadata`, no `reference_id`, no internal ids. Piping it straight through does not leak your own rate card to your own customer.

Costs your key's plan rate limit, not the anonymous 120/min the token route runs on — that bucket is keyed on IP, so proxying every payer through one backend would share one bucket between all of them.

Available from the Growth plan up; test keys are never gated.

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.

### Responses

- `200` — The checkout document.
  - `object` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `merchant` (required, object)
    - `name` (required, string) — The merchant's display name.
    - `logo_url` (required, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `currency` (required, string) — The pricing currency. USD today.
  - `price` (required, string) — Decimal string, 2 dp. What is owed.
  - `payer_amount` (required, string) — Decimal string, 2 dp. What the payer is asked to send (roadmap #2). Equals `price` unless this invoice carries a payer fee or markup; it is the amount the quotes render. NOT a fee field — this surface publishes amounts, never fees.
  - `received` (required, string) — Decimal string, 2 dp. Credited so far.
  - `remaining` (required, string) — Decimal string, 2 dp. Zero inside the ±0.5% tolerance band — a payer 3¢ short on a $100 invoice is `paid` and owes nothing.
  - `excess` (required, string) — Decimal string, 2 dp. Received beyond the price, outside the band.
  - `expires_at` (required, string (date-time))
  - `window_started_at` (required, string (date-time)) — When the window ending at `expires_at` began: the invoice's creation, or the last extension. Draw a countdown as (expires_at − now) / (expires_at − window_started_at), so a reload shows the time already spent rather than a full bar.
  - `extended` (required, boolean) — The window was extended to let an underpaying payer send the rest. Say so on screen — a timer that silently jumps back up reads as a bug.
  - `overpayment_policy` (required, string, one of: keep, refund) — What happens to money beyond the price. State it BEFORE the payer sends, and never call a kept overpayment a refund.
  - `selected` (optional, object | null) — What the payer picked. A display preference — it locks nothing and starts no clock.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
  - `options` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `name` (optional, string) — Display name for the asset.
    - `decimals` (required, integer) — 🔴 The decimals this amount was COMPUTED with. Render `amount_atomic` through this and nothing else — USDT/USDC on BSC are 18, not 6.
    - `address` (required, string) — Where the payer sends. Identical across EVM chains — the URI is what names the network.
    - `memo` (optional, string | null) — Required to attribute the payment on the chains that use one.
    - `amount` (required, string) — Human-readable form of `amount_atomic`. Derived; never compare against it.
    - `amount_atomic` (required, string) — Exactly what the payer must send.
    - `rate` (required, string) — Units of the pricing currency per whole unit of `asset`.
    - `stable` (required, boolean) — A par asset (USDT/USDC). Quoted once for the window and never refreshed.
    - `refresh_at` (optional, string | null (date-time)) — When this quote is replaced. NULL for a stable asset, which has no refresh.
    - `uri` (optional, string | null) — 🔴 The payment URI for the QR — `bnb:` on BSC, `ethereum:<contract>@<chainId>/transfer?…` elsewhere on EVM, TIP-681 on Tron, BIP-21 on UTXO. NULL on Solana and TON, which have no scheme worth emitting; show the bare `address` there. Our EVM address is the same on every chain, so this string is the ONLY thing in the QR naming the network.
  - `payments` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `amount` (required, string) — In units of `asset`, human-readable.
    - `tx_hash` (required, string)
    - `explorer_url` (optional, string | null)
    - `confirmations` (required, integer)
    - `required_confirmations` (required, integer)
    - `status` (required, string, one of: pending, confirmed, failed) — `pending` is seen on chain but not yet final. It is not money until `confirmed`. 🔴 `confirmed` is the CHAIN's verdict on one transfer, not the invoice's: a transfer is confirmed here for the window between finality and the invoice being credited, so read the checkout's own `status` for whether the order is paid.
    - `at` (required, string (date-time))
    - `receipt_no` (optional, string | null) — The payer's receipt number, `R-` + ten characters. Random, never sequential. NULL until the payment is booked against the invoice — a transfer the chain has confirmed does not have one yet.
  - `refund` (optional, object) — Present only when a refund of an overpayment exists on this invoice.
    - `status` (required, string, one of: awaiting_address, pending, sent, failed, skipped) — `awaiting_address` = the payer has not yet said where it goes. Nothing is sent until they do.
    - `skip_reason` (optional, string | null) — `uneconomical` when the network fee would have exceeded the refund. There is no form to show.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `gross` (required, string) — The excess, in units of `asset`.
    - `fee` (required, string | null) — The network cost of returning it, taken OUT of the refund. 🔴 NULL MEANS "NOT PRICED YET", NOT "FREE". Gas is quoted a few seconds after the overpayment and the quote can fail transiently. Show a working state on null; rendering a zero would promise the payer the whole excess and then hand them less.
    - `net` (required, string | null) — What the payer actually receives: `gross` minus `fee`. Null whenever `fee` is null.
    - `sender_class` (required, string | null, one of: personal, exchange, unknown, null) — What the payment looked like it came from. Only `personal` is ever prefilled — a refund into an exchange's shared hot wallet lands in the exchange's own float and credits nobody. 🔴 NULL MEANS "NOT WORKED OUT YET", NOT `unknown`. A refund row exists from the moment of overpayment; classifying who paid takes a few seconds longer. Show a working state on null and poll — rendering the blank "paste a wallet you control" form would be asking the payer to answer a question we have not finished asking. `POST …/refund` refuses with `refund_not_ready` until this is non-null.
    - `suggested_address` (optional, string | null) — 🔴 Populated ONLY for `sender_class: personal`. Prefill this and nothing else; never fall back to an address off a transaction. Always null while `sender_class` is null.
    - `to_address` (optional, string | null) — Where it is going. NULL until the payer confirms one.
    - `tx_hash` (optional, string | null)
    - `explorer_url` (optional, string | null)
  - `return_url` (optional, string | null)
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `invoice_not_found` — also what another merchant's invoice id returns.
- `422` — `embedded_checkout_not_on_plan` — this plan redirects the payer to the hosted page instead.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Relay the chain and asset your payer picked

`POST /v1/invoices/{id}/checkout/select`

The authenticated twin of `POST /v1/checkout/{token}/select`, for a page you render yourself.

🔴 IT LOCKS NOTHING AND STARTS NO CLOCK. The invoice's window begins at creation, and one address serves every EVM chain, so money arriving on any offered chain settles regardless of what was sent here. It is a display preference and a hint that tells the re-quoter which asset somebody is actually reading.

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.

### Request body (required)

- `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
- `asset` (required, string)

### Responses

- `200` — The checkout document, with `selected` set.
  - `object` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `merchant` (required, object)
    - `name` (required, string) — The merchant's display name.
    - `logo_url` (required, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `currency` (required, string) — The pricing currency. USD today.
  - `price` (required, string) — Decimal string, 2 dp. What is owed.
  - `payer_amount` (required, string) — Decimal string, 2 dp. What the payer is asked to send (roadmap #2). Equals `price` unless this invoice carries a payer fee or markup; it is the amount the quotes render. NOT a fee field — this surface publishes amounts, never fees.
  - `received` (required, string) — Decimal string, 2 dp. Credited so far.
  - `remaining` (required, string) — Decimal string, 2 dp. Zero inside the ±0.5% tolerance band — a payer 3¢ short on a $100 invoice is `paid` and owes nothing.
  - `excess` (required, string) — Decimal string, 2 dp. Received beyond the price, outside the band.
  - `expires_at` (required, string (date-time))
  - `window_started_at` (required, string (date-time)) — When the window ending at `expires_at` began: the invoice's creation, or the last extension. Draw a countdown as (expires_at − now) / (expires_at − window_started_at), so a reload shows the time already spent rather than a full bar.
  - `extended` (required, boolean) — The window was extended to let an underpaying payer send the rest. Say so on screen — a timer that silently jumps back up reads as a bug.
  - `overpayment_policy` (required, string, one of: keep, refund) — What happens to money beyond the price. State it BEFORE the payer sends, and never call a kept overpayment a refund.
  - `selected` (optional, object | null) — What the payer picked. A display preference — it locks nothing and starts no clock.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
  - `options` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `name` (optional, string) — Display name for the asset.
    - `decimals` (required, integer) — 🔴 The decimals this amount was COMPUTED with. Render `amount_atomic` through this and nothing else — USDT/USDC on BSC are 18, not 6.
    - `address` (required, string) — Where the payer sends. Identical across EVM chains — the URI is what names the network.
    - `memo` (optional, string | null) — Required to attribute the payment on the chains that use one.
    - `amount` (required, string) — Human-readable form of `amount_atomic`. Derived; never compare against it.
    - `amount_atomic` (required, string) — Exactly what the payer must send.
    - `rate` (required, string) — Units of the pricing currency per whole unit of `asset`.
    - `stable` (required, boolean) — A par asset (USDT/USDC). Quoted once for the window and never refreshed.
    - `refresh_at` (optional, string | null (date-time)) — When this quote is replaced. NULL for a stable asset, which has no refresh.
    - `uri` (optional, string | null) — 🔴 The payment URI for the QR — `bnb:` on BSC, `ethereum:<contract>@<chainId>/transfer?…` elsewhere on EVM, TIP-681 on Tron, BIP-21 on UTXO. NULL on Solana and TON, which have no scheme worth emitting; show the bare `address` there. Our EVM address is the same on every chain, so this string is the ONLY thing in the QR naming the network.
  - `payments` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `amount` (required, string) — In units of `asset`, human-readable.
    - `tx_hash` (required, string)
    - `explorer_url` (optional, string | null)
    - `confirmations` (required, integer)
    - `required_confirmations` (required, integer)
    - `status` (required, string, one of: pending, confirmed, failed) — `pending` is seen on chain but not yet final. It is not money until `confirmed`. 🔴 `confirmed` is the CHAIN's verdict on one transfer, not the invoice's: a transfer is confirmed here for the window between finality and the invoice being credited, so read the checkout's own `status` for whether the order is paid.
    - `at` (required, string (date-time))
    - `receipt_no` (optional, string | null) — The payer's receipt number, `R-` + ten characters. Random, never sequential. NULL until the payment is booked against the invoice — a transfer the chain has confirmed does not have one yet.
  - `refund` (optional, object) — Present only when a refund of an overpayment exists on this invoice.
    - `status` (required, string, one of: awaiting_address, pending, sent, failed, skipped) — `awaiting_address` = the payer has not yet said where it goes. Nothing is sent until they do.
    - `skip_reason` (optional, string | null) — `uneconomical` when the network fee would have exceeded the refund. There is no form to show.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `gross` (required, string) — The excess, in units of `asset`.
    - `fee` (required, string | null) — The network cost of returning it, taken OUT of the refund. 🔴 NULL MEANS "NOT PRICED YET", NOT "FREE". Gas is quoted a few seconds after the overpayment and the quote can fail transiently. Show a working state on null; rendering a zero would promise the payer the whole excess and then hand them less.
    - `net` (required, string | null) — What the payer actually receives: `gross` minus `fee`. Null whenever `fee` is null.
    - `sender_class` (required, string | null, one of: personal, exchange, unknown, null) — What the payment looked like it came from. Only `personal` is ever prefilled — a refund into an exchange's shared hot wallet lands in the exchange's own float and credits nobody. 🔴 NULL MEANS "NOT WORKED OUT YET", NOT `unknown`. A refund row exists from the moment of overpayment; classifying who paid takes a few seconds longer. Show a working state on null and poll — rendering the blank "paste a wallet you control" form would be asking the payer to answer a question we have not finished asking. `POST …/refund` refuses with `refund_not_ready` until this is non-null.
    - `suggested_address` (optional, string | null) — 🔴 Populated ONLY for `sender_class: personal`. Prefill this and nothing else; never fall back to an address off a transaction. Always null while `sender_class` is null.
    - `to_address` (optional, string | null) — Where it is going. NULL until the payer confirms one.
    - `tx_hash` (optional, string | null)
    - `explorer_url` (optional, string | null)
  - `return_url` (optional, string | null)
- `400` — `invalid_body`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `invoice_not_found`.
- `409` — `invoice_closed` — the invoice is paid, canceled, expired or refunded.
- `422` — `invalid_selection` — this invoice does not offer that chain and asset. `embedded_checkout_not_on_plan`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Relay where your payer wants an overpayment sent

`POST /v1/invoices/{id}/checkout/refund`

The authenticated twin of `POST /v1/checkout/{token}/refund`.

🔴 IF YOU RENDER YOUR OWN PAGE, YOU MUST BUILD THIS FORM. An embedded checkout that shows the amount and the address but never asks the question leaves an overpayment sitting in `awaiting_address` for ever — the payer is never prompted, and the money is stranded by an omission that looks like nothing at all from your side. Watch for `status: "overpaid"` with a `refund` block.

🔴 YOUR API KEY IS NOT YOUR PAYER'S CONSENT. Every gate the payer-facing route applies applies here unchanged: the address is validated through the destination chain's own adapter rather than a regex, an address this service custodies is refused outright, and nothing is accepted until we have classified who paid and priced what the send costs (`refund_not_ready` until then). It is still the payer's money and still a transfer nobody can take back.

Wait for BOTH `refund.sender_class` and `refund.net` to be non-null before you show the form. Idempotent on a repeat of the SAME address; a different one is refused once the refund is `pending`.

Ten a minute, matching the payer-facing route.

### Parameters

- `id` (path, required, string (uuid)) — The invoice's public id.

### Request body (required)

- `address` (required, string) — A wallet the payer controls, on the refund's chain.

### Responses

- `200` — The checkout document, with the refund now `pending`.
  - `object` (required, string)
  - `status` (required, string, one of: open, underpaid, paid, overpaid, expired, late, canceled, refunded)
  - `merchant` (required, object)
    - `name` (required, string) — The merchant's display name.
    - `logo_url` (required, string | null)
  - `title` (optional, string | null)
  - `description` (optional, string | null)
  - `currency` (required, string) — The pricing currency. USD today.
  - `price` (required, string) — Decimal string, 2 dp. What is owed.
  - `payer_amount` (required, string) — Decimal string, 2 dp. What the payer is asked to send (roadmap #2). Equals `price` unless this invoice carries a payer fee or markup; it is the amount the quotes render. NOT a fee field — this surface publishes amounts, never fees.
  - `received` (required, string) — Decimal string, 2 dp. Credited so far.
  - `remaining` (required, string) — Decimal string, 2 dp. Zero inside the ±0.5% tolerance band — a payer 3¢ short on a $100 invoice is `paid` and owes nothing.
  - `excess` (required, string) — Decimal string, 2 dp. Received beyond the price, outside the band.
  - `expires_at` (required, string (date-time))
  - `window_started_at` (required, string (date-time)) — When the window ending at `expires_at` began: the invoice's creation, or the last extension. Draw a countdown as (expires_at − now) / (expires_at − window_started_at), so a reload shows the time already spent rather than a full bar.
  - `extended` (required, boolean) — The window was extended to let an underpaying payer send the rest. Say so on screen — a timer that silently jumps back up reads as a bug.
  - `overpayment_policy` (required, string, one of: keep, refund) — What happens to money beyond the price. State it BEFORE the payer sends, and never call a kept overpayment a refund.
  - `selected` (optional, object | null) — What the payer picked. A display preference — it locks nothing and starts no clock.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
  - `options` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `name` (optional, string) — Display name for the asset.
    - `decimals` (required, integer) — 🔴 The decimals this amount was COMPUTED with. Render `amount_atomic` through this and nothing else — USDT/USDC on BSC are 18, not 6.
    - `address` (required, string) — Where the payer sends. Identical across EVM chains — the URI is what names the network.
    - `memo` (optional, string | null) — Required to attribute the payment on the chains that use one.
    - `amount` (required, string) — Human-readable form of `amount_atomic`. Derived; never compare against it.
    - `amount_atomic` (required, string) — Exactly what the payer must send.
    - `rate` (required, string) — Units of the pricing currency per whole unit of `asset`.
    - `stable` (required, boolean) — A par asset (USDT/USDC). Quoted once for the window and never refreshed.
    - `refresh_at` (optional, string | null (date-time)) — When this quote is replaced. NULL for a stable asset, which has no refresh.
    - `uri` (optional, string | null) — 🔴 The payment URI for the QR — `bnb:` on BSC, `ethereum:<contract>@<chainId>/transfer?…` elsewhere on EVM, TIP-681 on Tron, BIP-21 on UTXO. NULL on Solana and TON, which have no scheme worth emitting; show the bare `address` there. Our EVM address is the same on every chain, so this string is the ONLY thing in the QR naming the network.
  - `payments` (required, array)
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `amount` (required, string) — In units of `asset`, human-readable.
    - `tx_hash` (required, string)
    - `explorer_url` (optional, string | null)
    - `confirmations` (required, integer)
    - `required_confirmations` (required, integer)
    - `status` (required, string, one of: pending, confirmed, failed) — `pending` is seen on chain but not yet final. It is not money until `confirmed`. 🔴 `confirmed` is the CHAIN's verdict on one transfer, not the invoice's: a transfer is confirmed here for the window between finality and the invoice being credited, so read the checkout's own `status` for whether the order is paid.
    - `at` (required, string (date-time))
    - `receipt_no` (optional, string | null) — The payer's receipt number, `R-` + ten characters. Random, never sequential. NULL until the payment is booked against the invoice — a transfer the chain has confirmed does not have one yet.
  - `refund` (optional, object) — Present only when a refund of an overpayment exists on this invoice.
    - `status` (required, string, one of: awaiting_address, pending, sent, failed, skipped) — `awaiting_address` = the payer has not yet said where it goes. Nothing is sent until they do.
    - `skip_reason` (optional, string | null) — `uneconomical` when the network fee would have exceeded the refund. There is no form to show.
    - `chain` (required, string, one of: ethereum, bsc, polygon, arbitrum, base, avalanche, tron, solana, ton, bitcoin, dogecoin, optimism, linea, zksync, scroll)
    - `asset` (required, string)
    - `gross` (required, string) — The excess, in units of `asset`.
    - `fee` (required, string | null) — The network cost of returning it, taken OUT of the refund. 🔴 NULL MEANS "NOT PRICED YET", NOT "FREE". Gas is quoted a few seconds after the overpayment and the quote can fail transiently. Show a working state on null; rendering a zero would promise the payer the whole excess and then hand them less.
    - `net` (required, string | null) — What the payer actually receives: `gross` minus `fee`. Null whenever `fee` is null.
    - `sender_class` (required, string | null, one of: personal, exchange, unknown, null) — What the payment looked like it came from. Only `personal` is ever prefilled — a refund into an exchange's shared hot wallet lands in the exchange's own float and credits nobody. 🔴 NULL MEANS "NOT WORKED OUT YET", NOT `unknown`. A refund row exists from the moment of overpayment; classifying who paid takes a few seconds longer. Show a working state on null and poll — rendering the blank "paste a wallet you control" form would be asking the payer to answer a question we have not finished asking. `POST …/refund` refuses with `refund_not_ready` until this is non-null.
    - `suggested_address` (optional, string | null) — 🔴 Populated ONLY for `sender_class: personal`. Prefill this and nothing else; never fall back to an address off a transaction. Always null while `sender_class` is null.
    - `to_address` (optional, string | null) — Where it is going. NULL until the payer confirms one.
    - `tx_hash` (optional, string | null)
    - `explorer_url` (optional, string | null)
  - `return_url` (optional, string | null)
- `400` — `invalid_body`.
- `401` — Missing, malformed, expired or revoked API key.
- `403` — The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.
- `404` — `invoice_not_found`. Also `not_found` — the invoice exists but has no refund to direct, which is the same answer the payer-facing route gives so that neither confirms anything the other would not.
- `409` — `already_sent` — the refund has gone out. `already_confirmed` — a different address is already queued. `refund_not_ready` — we have not finished working out who paid, or what it costs to send; poll the GET and retry.
- `422` — `invalid_address` — not valid for that chain, or an address this service custodies. `embedded_checkout_not_on_plan`.
- `429` — Rate limit or quota exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.