Skip to main content
Most merchants never touch the API — the embedded checkout calls it for you. Use it directly if you’re building a custom payment flow, a mobile app, or server-side order creation.
Everything live today is a test network. Nothing is deployed to any mainnet yet, so every payment you create through this API settles in testnet money. Every example on these pages uses Base Sepolia, chain ID 84532. Roadmap.

Base URL

Every endpoint lives under one base URL. It is our shared edge-function host — the same host for every merchant, not a value issued per account. The API & SDK tab in the dashboard gives you your API key and the embed snippet. It does not list the base URL today, and the webhook signing secret shown in Settings → Webhooks is a read-only field that nothing provisions yet — ask us for either rather than guessing. Webhooks. Every example on these pages uses an environment variable for it:
All endpoints are POST and accept and return JSON.
Keep the base URL in configuration rather than hardcoding it. It is infrastructure, not part of the contract, and pinning it into source is the thing that makes a future change painful.

Authentication

There is no bearer token on these endpoints. They are public by design — the customer’s browser calls them directly — so what gates them is your origin allowlist and the identifiers in the body, not a secret in a header.
string
required
The origin making the call. It must be on your account’s allowed-origins list, or the request is refused with 403. Browsers set this automatically; a server-side caller has to send it explicitly.
string
required
Identifies your account. On the payment endpoints the quoteId carries it forward.
string
required
application/json
Origin is not optional. A request with no Origin header at all is refused exactly like one from an unlisted origin — 403, Origin not allowed for this merchant. Add every origin you will call from, including your server’s, in Settings before you script anything against these endpoints.
Examples elsewhere in this reference also send an apikey header, as $CC_PUBLISHABLE_KEY. That is the project key our browser SDK sends; these endpoints accept the call with or without it, and it does not identify your account. The allowed-origins list is the control, not that header.
The API key on the API & SDK tab is a different thing. The sk_live_… value there is a real secret, but none of the endpoints documented here read it — it is sent as X-Api-Key (that header name, matched case-insensitively; never apikey) to a separate server-to-server session endpoint that the current checkout does not use. Keep it on your server, and don’t reach for it to authenticate the calls above.

Conventions

Amounts are base units

Token amounts on the payment and status endpoints — expectedAmount, confirmedAmount — are strings in the token’s smallest unit. On most chains USDC, USDT, and EURC use 6 decimals, so "49000000" is 49.00.The quote is the exception. stablecoinAmount from create-quote is a plain decimal string, e.g. "53.165". Convert it yourself if you need base units — creating the payment does that conversion for you.Coming Quoted amounts will be rounded to two decimals at the moment the quote is made, which changes the value this endpoint returns — a figure like the three-decimal one above will come back with two. Read the amount the API gives you and pass it through; don’t re-derive it from your cart total or round it again on your side, or your figure and ours will disagree. It will be announced before it ships. Roadmap.Do not hardcode 6. Decimals vary by chain — USDT on BNB Chain mainnet is 18-decimal, though every network live today settles 6-decimal tokens. Use the tokenDecimals field returned with the payment.Strings, not numbers — a large amount would lose precision as a float.
Cart totals are ordinary decimal numbers: 49.00.
The IDs you can quote against today are 84532 Base Sepolia, 11155111 Ethereum Sepolia, 421614 Arbitrum Sepolia, 11155420 OP Sepolia, 80002 Polygon Amoy, 43113 Avalanche Fuji, 97 BNB testnet, and TRON Nile. Which of them you can use depends on your account: not every token exists on every one. Chains and tokens.The mainnet IDs each network will use once it opens — 1 Ethereum, 8453 Base, 42161 Arbitrum, 10 Optimism, 137 Polygon, 56 BNB, 43114 Avalanche — are not usable today. Quote one and you get 422 with chain_not_activated.A quote is only accepted for a chain activated on your account, so a test network you haven’t enabled returns that same 422.
UTC, but the exact shape varies: values come back with a +00:00 offset rather than a trailing Z, and some carry microsecond precision — 2026-08-26T06:07:33.083+00:00, 2026-08-26T06:02:33.186021+00:00.Parse with a real ISO 8601 parser. Don’t match on a trailing Z or assume milliseconds.
EVM casing varies by field: your pool address comes back checksummed, a one-time deposit address comes back lowercase. Compare EVM addresses case-insensitively and never rely on the casing you were served.TRON addresses are base58 starting with T and are case-sensitive — never normalise them.

Typical flow

Create a quote

Locks a price for a short window and produces a payment ID. create-quote.

Create a payment

Produces either a pool address to sign against, or a one-time deposit address. create-payment.

Wait for confirmation

Use webhooks — fulfil at capture: pool.deposit.confirmed when the customer sent a plain transfer, pool.swept when they paid from a connected wallet. Poll the status endpoint only as a backstop.

Rate limits

Rate limited per calling IP address, in fixed one-minute windows — not per account. Quoting and payment creation allow roughly 30 requests a minute from one IP; status polling allows roughly 120. Exceeding either returns 429 with a Retry-After header — respect it and back off. Because the limit is per IP, a server-side integration puts all of your traffic behind one bucket. Quote when a customer reaches checkout rather than on every page view.

Do not build these yourself

Things the embedded checkout does for you that a custom flow has to do for itself. At least these:On-chain verification. When you pin a settlementAnchor in the embed, the widget reads your payout address from the blockchain, re-derives the settlement address from that chain-read value, and refuses to proceed on a mismatch. Without an anchor it shows an unverified badge and does not block. A custom flow has to run that derivation itself, or it has no such protection at all. How it works.Fulfilment from webhooks. Never from a client-side response.Re-quoting and backoff. The widget re-quotes before a locked price lapses, and honours Retry-After on a 429.
Unless you have a specific reason, embed the checkout instead.

Errors

Every error code and what to do about it.