checkout402_
Architecture

Architecture — a machine-native API commerce layer#

What Checkout402 is, what it deliberately isn't, and the delta between this document and the code today.

Supersedes an earlier draft that generalised into physical ecommerce. That direction was rejected for good reasons, the sharpest being that non-custodial settlement and physical returns are structurally incompatible: money reaches the seller's wallet immediately, so there is nothing left to refund from. The wedge below has no such contradiction — delivery is instant and verifiable.


The invariant#

Everything here must preserve this, unchanged, forever:

curl API → 402 → pay → retry → success

No buyer account. No identity handshake. No custody. A feature that cannot be added without breaking this line is not a feature we add.

The test for any proposal: does an anonymous agent with a wallet and no prior relationship still complete a purchase in two requests?


Positioning#

Sell your API to AI agents. For developers: machine-native pay-per-use billing for any API.

Not x402 infrastructure, not an identity protocol, not a checkout system, not a wallet. Those are implementation details or future options, and naming the product after any of them describes the plumbing rather than the job.


The one primitive: Offer#

An Offer is what resource is sold, under what economic conditions. Nothing else.

{
  "id": "offer_123",
  "resource": { "method": "POST", "url": "https://api.acme.com/generate" },
  "price": { "amount": "0.04", "currency": "GBP" },
  "requirements": [
    { "type": "payment", "status": "unsatisfied",
      "amount": { "value": "0.04", "currency": "GBP" } }
  ]
}

No carts, inventory, shipping, tax, variants or fulfilment logic. If a field does not affect what is sold or what it costs, it does not belong here yet.

Today's Checkout row already is this, plus a purchase attempt bolted on. The rename is honest and cheap; the split is deferred until something needs it.


Requirements — the extensible seam#

The rule that lets this grow without redesign:

A machine action is executable once its requirements are satisfied.

Payment is the first requirement type and, for now, the only one. Later a seller might add {"type": "claim", "claim": "verified_organisation"} — and the protocol does not change, because the shape was always a list.

This is the whole extensibility story. It is deliberately a list of typed conditions, not a state machine, because a state machine forces every purchase through every state and the fast path must stay two requests.


Identity: primitives, not a product#

Five concepts, kept separate so none becomes a mandatory flow:

Principal the economic entity responsible (anonymous, Acme Ltd)
Actor the software acting (agent_123, a browser, a script)
Subject a stable payer reference — not legal identity
Credential evidence for a claim (key, JWT, wallet signature)
Authority evidence an actor may act for a principal, with limits
Policy what an actor may do (max £10/request)

Level 0 — anonymous — is the default and must remain possible at every level above it. Levels exist so a seller who asks "give this buyer 20% off" gets a subject, and one who asks "only approved enterprises" gets a claim.

Identity grows out of a seller's commercial question. It is never the answer to a question nobody asked.

Payment must work without identity. Identity may enhance payment; it may never redefine it. The system asks can this request satisfy the economic requirements? — not who are you?


HTTP semantics#

Keep them clean and unoverloaded:

401 authentication required
403 authenticated, not permitted
402 payment required

Errors as application/problem+json (RFC 9457), carrying the requirement that is unsatisfied and the call that satisfies it.


Discovery#

GET /.well-known/checkout402      → seller, offers URL, capabilities
GET /v1/sellers/{slug}/offers     → the machine-readable catalogue
GET /{slug}                       → the same catalogue, human face

Two faces again, the same as /c/{id}. A machine gets JSON; a person gets a page they can read and share.

This is plausibly the strongest network effect available: a deterministic way for an agent to find out that an API is purchasable at all, and what it sells.


Receipts#

Every settled purchase produces one, and the shape must not change as identity gets richer:

{
  "id": "receipt_123",
  "offer": "offer_456",
  "payment": { "id": "pay_789", "amount": {"value": "0.04", "currency": "GBP"} },
  "subject": "subject_xyz",          // optional, Level 1+
  "timestamp": "..."
  // later, without a protocol change: principal, actor, authority
}

Receipts are what eventually make audit, reconciliation, spend history and reputation possible — so the fields are reserved now even though nothing reads them.


Idempotency#

Machine clients retry by default, so this is not a later concern.

Idempotency-Key: agent-run-72831 on offer creation, payment attempts, and execution.

A payment must never execute the paid resource twice unless the seller opted in. The existing payment_payload_hash UNIQUE constraint is exactly the right primitive — a database constraint rather than a check-then-act, so concurrent retries lose at the database instead of racing.


Stateless fast path, optional stateful Intent#

The stateless flow stays the preferred one. Intent is an escalation, not a pipeline everything is forced through:

POST /v1/intents { "offer": "offer_123" } → { "status": "requires_payment" }

Worth it only for larger payments, expiring pricing, multiple requirements, delegated authority, or stronger auditability. Making it mandatory would cost the invariant at the top of this page.


Payments, internally neutral#

Model Payment, PaymentMethod, PaymentRequirement, PaymentProof, Receipt — with PaymentMethod = x402 today.

No wallet, no custody. The agent's own wallet pays; we verify and the seller is paid directly. Policy, authorisation, routing, verification and receipts are all buildable without holding anyone's money, and holding it brings licensing and operational weight that no current demand justifies.


Sellers first#

Do not try to win both sides at once. The buyer side stays compatible with any external x402-capable client.

The seller's actual problem: "I built an API, agents can use it, I want to charge per call, and API keys and subscriptions are awkward."


Delta from the code today#

Exists and correct: the 402 → pay → retry loop · settlement, replay protection, receipts · offer-card metadata (input_schema, output_schema, freshness, …) · relay with method, params, and encrypted upstream credentials · seller auth, keys, MCP + OAuth · non-custodial split via the deployed splitter.

Missing, in build order:

  1. /.well-known/checkout402 — discovery
  2. Seller profile/v1/sellers/{slug}/offers + /{slug}, two faces
  3. application/problem+json — errors currently use a bespoke detail/help shape
  4. Idempotency-Key on create (payment replay already guarded)
  5. subject on receipts — Level 1, optional, unread for now
  6. Seller analytics — revenue, buyers, calls per offer

Explicitly not building: carts, inventory, shipping, tax, Shopify or WooCommerce, cards, custody, returns, refund orchestration, ACP/UCP checkout, agent wallet, KYC, DID, standalone identity.

Those are not permanently excluded. They are excluded until a seller asks.


The moat#

Assume basic 402 middleware becomes commoditised — it should, it is a few hundred lines. The defensible layer is everything around it:

onboarding → offers → discovery → pricing → rails → execution
           → receipts → analytics → buyer history

Own the commercial layer around paid machine APIs, not the status code.