x402 pay-per-request
A protocol for agents that already have a wallet provider with spending policies: pay each image, video or audio request in USDC on Solana, with no account.
Using Claude Code, Codex, Cursor or another coding agent? Sign in instead: the agent spends your account balance within the monthly cap you pick, media included. Never create a wallet or store a private key on your computer for this.
x402 is for agents whose payments already go through a wallet provider with spending policies, such as a hosted or custodial agent wallet that signs within per-payment and daily limits. Your client quotes, shows the price, and hands the payment requirements to that provider; the provider signs or refuses.
Available on POST /v1/images/generations, POST /v1/media/generations and the media tools of the open MCP endpoint /mcp. Chat, embeddings and streaming need an account.
- Send the request with no
Authorizationheader. - We answer
402 Payment Requiredwith aPAYMENT-REQUIREDheader (x402 v2): USDC on Solana devnet, the exact amount for this request, and our pay-to address. - Your wallet provider signs the USDC transfer (within its spending policy) and your client retries the same request with a
PAYMENT-SIGNATUREheader (X-PAYMENTalso works). - We verify and settle the payment, then run the model. The response carries a
PAYMENT-RESPONSEreceipt with the transaction.
- The amount is quoted from the request itself: the model and its image count, video seconds and resolution, or audio length. Change the body and you get a new quote.
- List price. x402 callers have no account, so they pay each model's list price.
- Your buyback token, per call. x402 calls are at the Noob rate: 0.5% of what you pay buys back your token, out of our margin. It is $INFX unless you add
X-Buyback-Token: <mint>(on the media endpoints a body field"buyback_token": "<mint>"works too; the header wins). The token must be eligible in our buyback index; an unknown or ineligible one is a400 invalid_buyback_token, before any payment is asked for. - The token is part of the quote: keep it identical on the paid retry, or the payment will not match.
- A model whose price isn't known until it runs (for example, priced by the tokens it writes) can't be prepaid (
400 x402_unsupported_model); use a key for those. - If a paid request fails, you still get the receipt: contact us with the
x-request-id.
# 1. call with no key: the answer is 402 + what to pay
curl -i https://xavainf.com/v1/images/generations \
-H "Content-Type: application/json" \
-d '{"model":"pruna/p-image","prompt":"a lighthouse at dusk"}'
# HTTP/1.1 402 Payment Required
# PAYMENT-REQUIRED: <base64 of the JSON below>
# {"x402Version":2,"accepts":[{"scheme":"exact","network":"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
# "asset":"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU","amount":"4250","payTo":"6Bt7cFqEYPZhMS661vAGkjcGpFGW85BCH9XicLYusBLf",
# "maxTimeoutSeconds":60,"extra":{"feePayer":"...","memo":"xm_...","paymentFlow":"upfront"}}]}
# 2. your wallet provider signs that USDC transfer (within its spending policy); retry the SAME body
curl https://xavainf.com/v1/images/generations \
-H "PAYMENT-SIGNATURE: <base64 signed payment>" \
-H "Content-Type: application/json" \
-d '{"model":"pruna/p-image","prompt":"a lighthouse at dusk"}'
# HTTP/1.1 200 OK
# PAYMENT-RESPONSE: <base64 receipt: success, transaction, payer>curl -i https://xavainf.com/v1/images/generations \
-H "X-Buyback-Token: <token mint>" \
-H "Content-Type: application/json" \
-d '{"model":"pruna/p-image","prompt":"a lighthouse at dusk"}'import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { ExactSvmScheme } from "@x402/svm/exact/client";
// signer: a Solana transaction signer from your wallet provider (a hosted or custodial agent wallet
// that enforces spending policies). Never load key bytes from a local file.
const client = new x402Client().register("solana:*", new ExactSvmScheme(signer));
const pay = wrapFetchWithPayment(fetch, client);
const res = await pay("https://xavainf.com/v1/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "pruna/p-image", prompt: "a lighthouse at dusk" }),
});
console.log(await res.json()); // { data: [{ url }] }budget rules
- Show the price before paying; never pay above the user's cap without asking.
- The wallet provider's spending policy is the hard limit; your client's cap may only be stricter.
- Never retry a paid request in a loop; keep the
x-request-idand the transaction for support.
over MCP
An unpaid media tool call returns an error whose structured content is the payment requirements. Retry the same call with the signed payment in _meta["x402/payment"]; the result carries the receipt in _meta["x402/payment-response"].