Concepts#
checkout402 has one object. Understanding it is most of understanding the product.
The Checkout#
A checkout is { price, payTo, what-you-get } — a URL with two faces.
Checkout = Offer + Payment + Fulfilment
| Part | What it means |
|---|---|
| Offer | What's for sale, described well enough that an agent can decide before paying |
| Payment | The price, the fee, and which rails you accept |
| Fulfilment | What the buyer actually receives once money moves |
There is no separate "product", "listing", or "catalogue" object. Deliberately: creating a checkout should not feel like creating a marketplace listing.
Two faces, one URL#
GET /c/chk_abc123 content-negotiates:
Accept |
You get |
|---|---|
text/html |
A pay page — price, description, a wallet button |
| anything else | HTTP 402 with accepts[], the fee split, and the offer card |
Same object, same price, same ID. The buyer's client decides which face makes sense.
The offer card#
The optional metadata that makes an unpaid 402 genuinely useful. All fields are optional — creating a checkout stays fast — but each one raises the chance an agent completes the purchase.
| Field | Answers |
|---|---|
title, description |
What is this? (required) |
input_schema |
What do I provide? |
output_schema |
What will I get back? |
example_input, example_output |
Can I see it before paying? |
freshness, coverage, source |
Is it suitable for my task? |
constraints |
What are the limits? |
These are commercial claims
freshness, coverage and source are assertions a buyer will rely on. If an agent fills them in on your behalf, it should propose them for your approval — not publish them as fact.
Fulfilment modes#
What happens after settlement. Money moves first, always.
| Mode | Behaviour | Use when |
|---|---|---|
inline |
Returns payload verbatim |
You're selling a key, a token, a fixed answer |
relay |
Calls your origin_url, forwards the response |
You're putting an existing API behind a paywall |
mcp |
Connects to your MCP server and calls one tool | You're selling tools to agents |
webhook |
Receipt only; you're notified async | Fulfilment happens in your own system |
You rarely name one. The mode is inferred from what you send: payload is
inline, origin_url is relay, and naming an mcp_tool makes it mcp.
Selling a whole MCP server is the one shape that cannot be inferred — a bare
URL is always a relay — so it needs fulfillment: "mcp" explicitly.
Settle-before-deliver is not configurable
The ordering — verify, settle, then release — isn't a setting. The opposite ordering isn't representable in the code.
Packages#
calls: N sells N deliveries for one payment. One on-chain settlement, then
a pk_… key that spends a counter — no signature, no gas, no confirmation wait
on any call after the first.
| Which modes | relay and mcp. inline replays free bytes; webhook we don't deliver |
| Minimum price | $1.00 when calls > 1. Single calls have no minimum |
| The key | One per payment, not per checkout. Shown once, only the hash is kept |
| Expiry | 30 days by default, package_ttl_days to change it |
The entitlement hangs off the payment, so it outlives the listing: it keeps
working after the checkout is paid, expired, or retired. Retiring stops new
sales; it does not confiscate what people already bought.
Who pays when a call fails#
| The seller's origin returns | Charged |
|---|---|
5xx, timeout, unreachable, 401/403/429 |
No — refunded |
400, 404, 422 — the buyer's request was wrong |
Yes |
A buyer-fault failure returns the origin's own status, not a 502, so it is
actionable. Refunding it would make "does record X exist?" free whenever the
answer is no — the seller's origin would absorb every probe and only hits would
be paid for.
GET /c/{id}/usage itemises every call with counted and fault, free, so
"was I charged for that one, and whose fault was it" is always answerable.
What you still owe#
A package is cash received against calls not yet served. c402 owed-calls and
the liabilities block on /v1/analytics show unspent calls per checkout, and
retiring a checkout tells you the same number rather than letting you forget it.
Two kinds of checkout#
A classic checkout settles atomically: the buyer's signature moves the money straight to the seller (and the fee, and a platform's cut) in one on-chain transaction. Final the moment it lands — that is most of the product.
An escrow checkout (esc_ ids, served at /e/{id}) holds the money in an
on-chain contract first. Exactly two exits exist, fixed the moment the buyer
pays: release to the seller, or refund to the buyer. By default it releases
automatically after the settle window; the buyer can pause, approve early, or
dispute — and a dispute nobody resolves refunds the buyer automatically, so
funds can never be stranded. Goods still deliver at payment; escrow changes
when the money becomes the seller's, not when the buyer gets the thing. Fee:
1% instead of 0.5%. Full model: Escrow.
A plain checkout can also offer protection (escrow_offer: "optional"):
both faces exist and the buyer chooses at pay time by which URL they sign.
Payments and receipts#
Every payment attempt writes a CheckoutPayment row with its own pay_… id, addressable at /r/pay_….
A receipt carries the payer, the seller, the exact split, the network, the transaction hash, and the delivery outcome. Two faces again: JSON for agents, HTML for humans.
The X-PAYMENT-RECEIPT header returns it inline on the paying request, so an agent gets its receipt in the same round trip as the goods.
Retrying is safe
Re-presenting the same X-PAYMENT redelivers the goods and never charges twice. A dropped connection after settlement is the worst failure in payments — the buyer has paid and has nothing — so the same signed authorization is idempotent by design.
Single-shot vs reusable#
| Behaviour | |
|---|---|
reusable: true |
Many payments, stays open. A payment link or a metered endpoint. What an agent creates by default — a published product is not spent by its first buyer. |
reusable: false |
One payment, then status: paid. An invoice to a particular buyer. The CLI and dashboard still default to this; pass it explicitly over MCP. |
Not to be confused with calls, which is the axis sellers usually mean by
"single": one call per payment, or a package of many. reusable is about how
many buyers the URL serves; calls is about how much one buyer gets.
The test face#
Every checkout carries a rehearsal address: /c/{id}/test. Same product,
same origin call, but settlement is simulated — synthetic 0xtest_… hash,
test_mode: true stamped on the payment and the receipt, no funds anywhere.
/pay and /prepare exist on both faces, so a whole client can point at the
test face unchanged.
Which face settles simulated is decided by the path and the armed state, never by the payment payload. A test payload presented to an armed live face is refused, not quietly simulated. And a rehearsal never flips the checkout's status — it cannot consume a single-shot or pollute the sale.
The faces cross-link inside the 402: a live face carries
endpoint.rehearsal.test_url; a test face says in words that it settles
without real money, and names live_url — or says the seller has not gone
live yet.
Origin-backed rehearsals (relay and mcp) are budgeted: 25 test
deliveries per checkout per day by default, test_delivery_daily_cap on
create to change it. Past the cap is a 429 pointing at the live URL. Inline
and webhook rehearsals call nothing, so they are unmetered.
Build unarmed, then go live#
test_mode: true on a checkout means not armed. Checkouts created over an
OAuth connection — Claude, or any agent without a c402_ key — and by
c402_test_ keys start unarmed: the bare URL rehearses too, so nothing an
agent makes for you can take real money until you say so.
Arming is its own step, in place:
c402 checkouts go-live chk_abc123
Same id, same URL, idempotent. The link you already shared is the one that now
takes real money — and the address that rehearses differs from it by a path
segment that says which is which, because /test can never be armed. Scripts
belong on the test face; buyers get the bare URL. One URL per product, always.
The dashboard has the same button under Manage → Go live, with the price and title in front of you while you decide.
The fee#
0.5% of the price, plus $0.01. Visible in the 402 before anyone pays, and split out on-chain in the same transaction that pays you.
Your price $0.10
Fee $0.0105 (0.5% of $0.10 = $0.0005, plus the $0.01 fixed part)
Buyer pays $0.1105
You receive $0.10 — always your full price
The fixed $0.01 is one on-chain settlement, not margin — it costs real money to land a transaction, and it costs the same whether the sale is $0.01 or $1,000. That is also why packages exist: a package settles once and serves many calls, so a $1 package of 1,000 calls pays the fixed part once instead of a thousand times. Selling calls for less than a cent each is a package, not a cheaper single call.
Single calls have no minimum price, deliberately. A cheap one is how a buyer tries your API before committing — $0.015 to find out, rather than $1.015.
On Base the split runs through an immutable splitter contract. On Solana there's no contract at all — the split is two SPL transfers inside the buyer's own transaction. Either way, checkout402 never holds the money.
Identity#
| Who | How | Why |
|---|---|---|
| Buyers | Nothing. The payment is the login. | No signup is the entire point |
| Sellers (human) | Magic link → session cookie | No password to leak |
| Sellers (agent) | Wallet signature → automation key | Agents have keys, not inboxes |
A signed wallet challenge is a verified identity here — bring-your-own-wallet is the product thesis, and the signature proves control of the payout address.