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

Dynamic pricing with quotes

Compute a price per request and still charge exactly what the payer was quoted.

Public Beta · early access
app.post(
  "/v1/translate",
  tollstile(toll.price(async (context) => {
    const words = await countWords(context.request);
    const micros = BigInt(words) * 100n;
    return { currency: "USD", micros };
  })),
  translate,
);
  1. A request without payment gets 402 with a signed quote for the computed price.
  2. The payer retries the same request with a proof that carries the quote back.
  3. Tollstile charges the quoted price, even if the function would now return a different one.

Quotes expire after quoteTtlMs (5 minutes by default) and are never stored. Dynamic routes require rails that can carry quotes; rails without quote support are excluded from the execution plan. The route is refused only if no compatible rail remains.

A quote only pays for the request it priced

A quote commits to the request it was issued for: method, path and query, and a hash of the exact body bytes. On MCP it commits to the tool name and its arguments. If the retry differs, Tollstile answers 402 with error code quote_mismatch and a fresh quote for the new request. Nothing is authorized or charged.

So this does not work:

POST /v1/translate   {"text": "hello"}                → 402, quote for $0.0001
POST /v1/translate   {"text": "<a million words>"}    → 402 quote_mismatch, quote for $100.00
  Payment: …quote for $0.0001…

Your price function and handler both read the body normally. Tollstile hashes a copy.

Clients that re-serialize the body

Binding to exact bytes means the retry must send the same bytes. If your clients may reorder keys or add fields that do not affect the price, bind only the fields that do:

toll.price(priceByWords, {
  commit: async (context) => {
    const { text, targetLanguage } = await context.request.json();
    return JSON.stringify([text, targetLanguage]);
  },
});

Anything the commitment leaves out can change after quoting, so include every input your price depends on.

Reusable credentials

Reusable credentials on quote-capable rails (such as L402) are charged each request's own price against their limit. They are never locked to the first request's quote.

Tollstile does not decide what your service should cost — it evaluates the price you compute.

KYAPay cannot carry quotes and is excluded from computed-price routes. If a price function returns upTo(), only planned rails with amounts: "up_to" can offer that price; if none remain, the request throws CAPABILITY_MISSING.

Retries

Send Idempotency-Key on the first paid request and keep it on retries of the same request (on MCP: _meta["tollstile/idempotency-key"]). A completed charge returns 409 already_paid; an in-flight charge returns 409 request_in_progress; an unknown outcome returns 503 payment_outcome_unknown. Do not start a new payment while the outcome is unknown. Released/refunded attempts may run again. See Idempotency for request matching and rail-specific key scopes.

On this page