# Checkout (/reference/checkout)

## What the payer's page renders

`GET /v1/checkout/{token}`

Unauthenticated. The `checkout_token` on the invoice (`ck_` + 43 base64url characters, returned as part of `checkout_url`) is the entire authorisation, so treat the link like a bearer credential: anyone holding it sees this document.

🔴 THIS IS NOT AN INVOICE, and the difference is deliberate. It carries no `metadata`, no `reference_id`, no internal ids and NO FEE of any kind — not the bps, not the fixed, not the cap, not a null one. A checkout link goes to strangers; publishing the fee would hand a merchant's negotiated rate to anyone who ever bought from them.

There is no separate status endpoint: poll this one. The page decays its polling (3s for the first two minutes, then 8s, then 20s), pauses on a hidden tab and catches up on `visibilitychange`.

Every miss — a malformed token, an unknown one, a deleted invoice — is the same 404 with no distinguishing detail.

### Parameters

- `token` (path, required, string) — The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`.

### 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)
- `404` — `not_found` — no checkout for this link.
- `429` — Rate limit exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## Record which chain and asset the payer picked

`POST /v1/checkout/{token}/select`

🔴 THIS LOCKS NOTHING AND STARTS NO CLOCK, whatever the name suggests. The invoice's window begins at creation, and one address serves every EVM chain, so money arriving on any offered chain settles against the invoice no matter what was selected here.

It is a display preference: it lets the page show one amount instead of six, and lets the re-quoter prioritise the asset somebody is actually reading. Returns the same document as the GET.

### Parameters

- `token` (path, required, string) — The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`.

### 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`.
- `404` — `not_found` — no checkout for this link.
- `409` — `invoice_closed` — the invoice is paid, canceled, expired or refunded.
- `422` — `invalid_selection` — this invoice does not offer that chain and asset.
- `429` — Rate limit exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.

## The payer says where an overpayment goes

`POST /v1/checkout/{token}/refund`

Only for an invoice whose merchant's `overpayment_policy` is `refund` and whose `refund` block is `awaiting_address`. Nothing is sent until this call: the address that paid is a SUGGESTION, offered as a prefill only when `sender_class` is `personal`, because a great many payments arrive from an exchange's shared hot wallet and a refund sent there lands in the exchange's float, credits nobody, and cannot be undone.

🔴 Wait for BOTH `refund.sender_class` and `refund.net` to be non-null before showing the form at all. Until then this route refuses with `refund_not_ready`. A null class means we are still working out who paid, and a payer must not choose a destination against advice that arrives a second later; a null `net` means we have not finished pricing the send, and the form promises a payer three lines — excess, network fee, what you receive — so accepting an address before the middle one exists has them agreeing to a figure nobody had worked out.

The address is validated for the refund's own chain through that chain's adapter — never a regex — and an address we ourselves custody is refused.

Idempotent on a repeat of the SAME address once the refund is `pending`; a DIFFERENT address is refused, because a queued refund must not be redirectable.

Rate limited to 10/minute: it is the most abusable route on the surface.

### Parameters

- `token` (path, required, string) — The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`.

### 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`.
- `404` — `not_found` — no checkout for this link, or nothing to refund.
- `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.
- `429` — Rate limit exceeded. Honour `Retry-After`.
- `500` — Unexpected error.
- `503` — A dependency is unavailable. Retryable with backoff.