Quotes
What the server offered — signed, immutable, and never stored.
Every 402 carries a quote: the resource, the price, whether it is a maximum, an offer per rail, a nonce, and an expiry. It is serialized as a compact token and signed with your secret.
{
"error": "payment_required",
"price": "$0.04",
"quote": "eyJ2IjoxLC….x9Q…",
"expiresAt": "2026-09-15T12:05:00.000Z",
"accepts": [{ "rail": "x402", "asset": { "code": "USDC", "network": "eip155:8453", "scale": 6 }, "amount": "40000", "flow": "authorization" }]
}Why quotes exist
- The price charged is the price the payer saw. Dynamic prices can change between the
402and the retry; the proof carries the quote back and Tollstile charges the quoted price. - Nothing is written for unpaid requests. Quotes are verified by signature, so the ledger only grows when someone actually pays.
- A quote pays only for the request it priced. It commits to the method, path, query, and body (or MCP tool arguments), so a cheap quote cannot be spent on a larger request.
- Evidence can bind to a request. The quote's
noncelets protocols such as AP2 bind a user mandate to this exact offer.
How rails carry a quote
Each rail puts the quote token inside its own protocol, where the payer's client echoes it: x402 in the requirement's extra, MPP in the challenge's opaque, L402 in a macaroon caveat, the test rail as quote=. Rails that cannot carry one declare quotes: false and serve fixed-price routes only.
Validation
A quote is honored only if its signature matches a configured secret, it has not expired, it was issued for the same resource, and the retried request matches its commitment. Rotate secrets by listing the new one first: secret: [next, previous].
Request commitment
commit | The quote is bound to | Default for |
|---|---|---|
"request" | method, resource, path and query, the exact body bytes; on MCP, the tool and its arguments | dynamic prices |
"route" | method and resource | fixed prices |
(context) => string | method, resource, and the value you return | bodies that clients re-serialize |
A mismatch returns 402 with reason: "quote_mismatch" and a fresh quote, before anything is written. Reusable authorizations are the exception: they pay each request's current price against their limit, so they are not held to one request. See Dynamic pricing.