Pay with x402

A wallet pays for one completion in USDC on Base. No API key and no Klinn account. The call is the same POST /v1/chat/completions.

How a request is paid

  1. 1
    Call with no key
    Send the completion with no Authorization header.
  2. 2
    Read the 402
    The API answers 402 Payment Required with a PAYMENT-REQUIRED header. That is the USDC price of this prompt plus the output cap.
  3. 3
    Sign and retry
    The agent signs the payment and repeats the request with PAYMENT-SIGNATURE. The answer is in that response.
The USDC amount is fixed before the first byte. A 404 or a 503 before any provider accepts is not settled. x402 does not pay decisions: a decisions call needs a model key. To add credit to an account instead, see Top-ups.

Prerequisites

  • A wallet that can sign an EIP-3009 USDC authorization on Base (eip155:8453). Any such wallet works, including a viem account. The private key stays on the agent.
  • An x402 client. The sample uses @x402/fetch and ExactEvmScheme from @x402/evm. Any client that reads PAYMENT-REQUIRED and retries with PAYMENT-SIGNATURE works. The older X-PAYMENT header is accepted too.

The only network is Base, the asset is USDC, and the scheme is exact. Settlement goes through the Coinbase facilitator.

The price of one call

Two numbers, found in two places. The catalog is the rate card: wholesale USD per million prompt tokens and per million completion tokens, with no key and no wallet (GET /v1/models, or GET https://app.klinn.dev/api/v1/models). The charge for one completion depends on this prompt and this output cap, so it comes back on the call itself.

accepts[0] names the scheme (exact), the network (eip155:8453, Base), the USDC asset, the payee, and amount in atomic units (6 decimals, so divide by 1,000,000). The resource description names the model and the output cap the amount covers. A paid response repeats the dollar amount in x-price-usd.

The quote is our price for that estimate. It uses the model's rates from the catalog, it is at least $0.001, and it is rounded up to a micro-dollar. The estimate counts the prompt at about four characters per token, plus the output cap. You pay the quote. max_tokens is 512 when you set none and 4,096 at most, and the provider is held to that cap, so the answer cannot run past what the price covers. Reasoning models spend output tokens thinking: give them 1,000 or more. A later provider that takes over after a failure keeps the same quote: the price belongs to the model. A keyed request is charged that same price per token, on the tokens it used.

A client on Base

import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const wallet = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);
const pay = wrapFetchWithPaymentFromConfig(fetch, { schemes: [{ network: "eip155:*", client: new ExactEvmScheme(wallet) }] });

const res = await pay("https://api.klinn.dev/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ model: "claude-sonnet-5", max_tokens: 400, messages: [{ role: "user", content: "Summarise this page" }] }),
});

wrapFetchWithPaymentFromConfig parses PAYMENT-REQUIRED, signs with the wallet you give it, and retries with PAYMENT-SIGNATURE. The completion comes back as soon as a provider accepts, and the USDC transfer is submitted in the background. The body carries x-price-usd. @x402/fetch returns that body either way: it reads a settlement header when one is present, and still hands back the body when the completion is already streaming.

Top-ups are different. They settle before they answer, and that response carries PAYMENT-RESPONSE with the transaction.

The same route as a key

Ranking is the usual one: the cheapest healthy provider for this token estimate, then the next if that one fails before the first byte. Routing fields (router, provider, x-router-*) are removed before ranking, so a paid call and a keyed call are ranked the same way.

Streaming

"stream": true is honored. The response is text/event-stream, the same chunks as a keyed request. stream_options.include_usage is forwarded, and a final usage chunk keeps prompt, completion and total tokens. The USDC amount stays the quote from the cap. The usage chunk reports the tokens the provider counted. The stream starts only after the signature verifies and a provider accepts. If the connection drops after that, the quoted amount is still settled, which is the same moment a keyed request is debited.