npm packages are not installable yet: [email protected] can be published after 2026-09-16 10:30 UTC. Details
Tollstile

Quotes

What the server offered — signed, immutable, and never stored.

Public Beta · early access

A payment challenge 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": { "code": "payment_required", "retryable": true, "action": "pay", "message": "Payment required.", "detail": null },
  "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

  • A single-use proof pays the quoted price. Dynamic prices can change between the 402 and 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. Computed prices default to a commitment over 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 nonce lets 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 are excluded from computed-price routes.

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

commitThe quote is bound toDefault for
"request"method, resource, path and query, the exact body bytes; on MCP, the tool and its argumentsdynamic prices
"route"method and resourcefixed prices
(context) => stringmethod, resource, and the value you returnbodies that clients re-serialize

A mismatch returns 402 with error code 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.

A requirement denial or a settlement rejection after the body was consumed can have status 402 without a fresh quote. Only compatible, currently available offers appear in accepts. Rails without quote support can serve static fixed and static upTo() prices when their other capabilities allow it, but not computed prices.

On this page