Unfamiliar with terms like EIP-3009, x402, AccessGrant, or ChallengeEngine? Read Core Concepts first.
Discover Plans
The client calls
GET /discover to browse available plans and pricing. This returns all plan IDs, USDC amounts, and the seller’s wallet address. No payment or challenge is created.Request Access and Pay
The client sends
POST /x402/access with a planId. The server creates a PENDING challenge and returns a 402 with the payment amount, destination wallet, and chain ID. The client signs an EIP-3009 authorization off-chain and retries with a payment-signature header. The server settles on-chain.Sequence Diagram
Phase 1 — Discovery
The client callsGET /discover to retrieve the full plan catalog. The response contains one entry per configured plan with its planId, USDC amount, seller wallet, and chain ID. No challenge record is created.
POST /x402/access without a planId returns HTTP 400 with a pointer to GET /discover — it is not the discovery endpoint.Phase 2 — Challenge
The client sendsPOST /x402/access with a resourceId and planId. The server looks up the matching plan, creates a PENDING challenge record in the challenge store, and returns a 402 containing:
- amount — the USDC amount to pay (in base units)
- destination — the seller’s wallet address
- chainId —
8453(Base mainnet) or84532(Base Sepolia) - challengeId — a unique identifier for this payment session
Phase 3 — Settlement and Grant
The client signs an EIP-3009 authorization off-chain and retriesPOST /x402/access with the same planId + requestId plus a payment-signature header. The server then:
- Verifies the ERC-20
Transferevent on Base matches the expected amount, destination, and chain. - Transitions the challenge from PENDING to PAID (atomic, prevents double-spend).
- Calls the seller’s
fetchResourceCredentialscallback to issue a credential (JWT, API key, or any token). - Transitions from PAID to DELIVERED and returns an
AccessGrantwith the token and resource URL.
ChallengeEngine, which enforces the state machine invariants and logs every transition for auditability.
Pay-Per-Call Variant
For paidroutes, the flow differs slightly: no JWT is issued. Instead, Key0 returns the API response directly.
Standalone (proxy mode)
AddproxyTo (or fetchResource) to SellerConfig. Clients call the paid route directly, receive a 402 challenge, then retry the same route with payment-signature.
Embedded (inline settlement)
Usekey0.payPerRequest(routeId) middleware on each route. No proxyTo needed — Key0 settles on-chain and calls next(), so your route handler runs as normal.
Both variants share the same PENDING → PAID → DELIVERED lifecycle with automatic refunds if delivery fails after on-chain settlement. See Payment Flow for the full state machine.
Two Endpoints, One Engine
Both entry points share the sameChallengeEngine instance, so payment logic, state management, and security invariants are identical regardless of how the client connects.
| Endpoint | Use Case |
|---|---|
POST /x402/access | Unified: x402 HTTP flow (default), A2A JSON-RPC (with X-A2A-Extensions header), or standalone per-request proxy |
POST /mcp | MCP Streamable HTTP (opt-in via mcp: true) |
GET /api/* | Embedded per-request — each route has key0.payPerRequest() middleware |
Next Steps
Paying for Access
The buyer’s perspective: sign EIP-3009, submit payment, and use the access token.
Payment Flow Details
Full walkthrough of every state transition and error path.
State Machine
The complete PENDING / PAID / DELIVERED / EXPIRED / REFUNDED state diagram.
Settlement Strategies
Direct transfer vs. EIP-3009 authorization and facilitator settlement.

