Skip to main content
This guide is written from the buyer’s perspective. If you are building the seller side, see Building a Seller. If you want a fully automated setup via Claude Code, see Claude Code Integration. By the end of this guide you will understand how to:
  1. Discover a Key0 service and its plans
  2. Request a payment challenge
  3. Sign an EIP-3009 authorization and submit payment
  4. Receive and use the AccessGrant token
  5. Handle common errors
New to Key0? Read Core Concepts first to understand terms like Plan, Challenge, AccessGrant, and EIP-3009.
Two buyer flows: This guide covers both.
  • Subscription plans (mode: "subscription", the default) — the server returns an AccessGrant containing a signed JWT. You use the JWT as a Bearer token to call protected endpoints repeatedly until it expires.
  • Pay-per-call routes — the server returns a ResourceResponse containing the actual API data. No JWT is issued; every call requires a fresh payment.
Steps 1–5 below cover the subscription flow. See Per-Request Flow at the bottom of this page for per-request.

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)
Never use your mainnet private key for development. Use a dedicated testing wallet with only testnet funds.

Step 1: Discover the service

Call GET /discover 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
Key fields to extract:
  • extra.planId — the ID to use when requesting access
  • amount — USDC micro-units (6 decimals): 100000 = $0.10
  • payTo — the seller’s USDC-receiving wallet address
  • network — CAIP-2 (Chain Agnostic Improvement Proposal) chain ID: eip155:84532 = Base Sepolia, eip155:8453 = Base mainnet
  • asset — the USDC ERC-20 contract address on that chain
Alternatively, fetch the A2A agent card for a human-readable description of the service and its skills:

Step 2: Request a challenge

Send POST /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.
Also check the 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

Field explanation:
  • from — your wallet address (the payer)
  • to — the seller’s wallet address (from payTo in the discovery response)
  • value — USDC amount in micro-units (from amount in the discovery response)
  • validAfter — earliest time the authorization is valid (use 0 for immediate)
  • validBefore — latest time (Unix timestamp); set to now + 300 seconds (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 the X402PaymentPayload 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

Retry POST /x402/access with the same planId, requestId, and resourceId, plus the payment-signature header:
Response (HTTP 200)
Key fields:
  • accessToken — the credential to use on the protected endpoint
  • resourceEndpoint — the URL to call with the token
  • txHash — the on-chain transaction hash (your receipt)
  • explorerUrl — link to view the transaction on Basescan

Step 5: Use the access token

Call resourceEndpoint with Authorization: Bearer <accessToken>:
curl equivalent

Error handling

Key0 errors follow a consistent JSON structure:
Common errors a buyer will encounter: See Error Codes for the complete list.

Full working example

The following is a complete runnable TypeScript script that performs the full discovery → challenge → payment → access flow using viem:

Per-Request Flow

For pay-per-call routes, every API call requires its own payment. No JWT is issued — the server returns the API response directly. Two deployment styles exist:

Standalone pay-per-call

The seller runs Key0 as a payment gateway. You call the paid route directly, Key0 returns 402, and after payment it proxies to the backend and returns a ResourceResponse with the data. Step 1: Discover paid routes Check the discovery response for the routes array:
Response
Step 2: Request a challenge Call the route directly:
Response is a standard 402 with payment requirements scoped to that exact route call. Step 3: Sign and submit (same as subscription) Sign an EIP-3009 authorization using the same process as Steps 3–4 in the subscription flow above, then retry the same route with payment-signature:
Step 4: Use the ResourceResponse directly Unlike a subscription, there is no token to store. The response contains the backend data:
Response (HTTP 200)
Use result.resource.body directly — no Bearer token needed, no follow-up request.
Every pay-per-call request requires a fresh payment with a unique requestId and EIP-3009 nonce. Never reuse them.

Embedded per-request

The seller uses key0.payPerRequest() middleware on individual routes. You call the route directly with a payment-signature header — there is no /x402/access involved. Step 1: Call the route without payment (get 402)
Response (HTTP 402)
Step 2: Sign payment and call again Use the accepts[0] values to sign an EIP-3009 authorization (same process as Step 3 in the subscription flow), then retry the same route with the payment-signature header:
Step 3: No token, no follow-up The route handler’s response is returned directly. For the next call to the same or a different route, start over with a fresh payment (new nonce, new payment-signature).

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.