- Discover a Key0 service and its plans
- Request a payment challenge
- Sign an EIP-3009 authorization and submit payment
- Receive and use the
AccessGranttoken - Handle common errors
New to Key0? Read Core Concepts first to understand terms like Plan, Challenge, AccessGrant, and EIP-3009.
What you need
- A wallet private key with USDC on Base Sepolia (testnet) or Base (mainnet). Get free testnet USDC from faucet.circle.com (select Base Sepolia).
- The seller’s base URL (e.g.,
https://api.example.com) - viem for EIP-3009 signing (
npm install viem)
Step 1: Discover the service
CallGET /discovery to browse available plans. This returns plan IDs, USDC amounts, the seller’s wallet address, and the chain ID — everything you need to construct a payment.
Response
extra.planId— the ID to use when requesting accessamount— USDC micro-units (6 decimals):100000= $0.10payTo— the seller’s USDC-receiving wallet addressnetwork— CAIP-2 (Chain Agnostic Improvement Proposal) chain ID:eip155:84532= Base Sepolia,eip155:8453= Base mainnetasset— the USDC ERC-20 contract address on that chain
Step 2: Request a challenge
SendPOST /x402/access with the planId (and optionally a requestId and resourceId). The server creates a PENDING challenge and responds with HTTP 402.
Response (HTTP 402)
Always supply a stable
requestId (UUID). If the request fails and you retry with the same requestId, the server returns the existing challenge instead of creating a duplicate. If you omit it, the server auto-generates one — but you lose safe retry behavior.payment-required response header — it contains a base64-encoded copy of the same payment requirements, which some x402-aware clients read automatically.
Step 3: Sign the EIP-3009 authorization
This is where the buyer pays — without sending a transaction. You sign an off-chain EIP-712 typed-data message authorizing a USDC transfer. The seller’s gas wallet submits the actual on-chain transaction and pays the gas fees.The EIP-3009 typed data structure
from— your wallet address (the payer)to— the seller’s wallet address (frompayToin the discovery response)value— USDC amount in micro-units (fromamountin the discovery response)validAfter— earliest time the authorization is valid (use0for immediate)validBefore— latest time (Unix timestamp); set tonow + 300seconds (5 minutes)nonce— a random 32-byte value that prevents replay attacks; generate a fresh one for every payment
Full signing example (TypeScript + viem)
Build the payment-signature header
Once you have the signature, assemble theX402PaymentPayload and base64-encode it:
The
accepted field must echo the PaymentRequirements from the 402 response. Key0 uses it to verify that the client agreed to the correct terms (amount, destination, network).Step 4: Submit payment and receive the AccessGrant
RetryPOST /x402/access with the same planId, requestId, and resourceId, plus the payment-signature header:
Response (HTTP 200)
accessToken— the credential to use on the protected endpointresourceEndpoint— the URL to call with the tokentxHash— the on-chain transaction hash (your receipt)explorerUrl— link to view the transaction on Basescan
Step 5: Use the access token
CallresourceEndpoint with Authorization: Bearer <accessToken>:
curl equivalent
Error handling
Key0 errors follow a consistent JSON structure:| Code | HTTP | Cause | Fix |
|---|---|---|---|
CHALLENGE_EXPIRED | 410 | The challenge TTL (default 15 minutes) elapsed before payment arrived | Start over: call POST /x402/access again to get a fresh challenge |
AMOUNT_MISMATCH | 400 | authorization.value doesn’t match the challenge amount | Re-read the amount from the 402 response and sign again |
TX_ALREADY_REDEEMED | 409 | The same transaction hash was submitted twice | Do not reuse nonce values; generate a fresh random nonce for every payment |
PROOF_ALREADY_REDEEMED | 200 | The requestId was already settled | The response body contains the cached AccessGrant — use it directly |
INVALID_PROOF | 400 | EIP-3009 signature verification failed | Check that domain.chainId, domain.verifyingContract, and domain.version match the network |
PAYMENT_FAILED | 402 | Settlement failed on-chain | Verify wallet has sufficient USDC balance and the EIP-3009 validBefore has not expired |
Full working example
The following is a complete runnable TypeScript script that performs the full discovery → challenge → payment → access flow using viem:Next Steps
Claude Code Integration
Use payments-mcp to automate the payment flow from Claude Code or Cursor.
x402 HTTP Flow
Full protocol reference: headers, request/response schemas, and three-cases breakdown.
A2A Flow
The JSON-RPC based agent-to-agent payment protocol.
Error Codes
Complete list of all Key0 error codes and their meanings.

