Charge for usage
Authorize a maximum per call and settle only what the handler actually used — for LLM, image, and video generation.
Public Beta · early access
import { upTo } from "tollstile";
app.post("/v1/render", tollstile(toll.price(upTo("$0.50"))), async (c) => {
const video = await render(await c.req.json());
await storage.put(video);
await c.get("payment").fulfill({ amount: costOf(video) }); // e.g. "$0.12"
return c.json(video);
});- The payer authorizes up to $0.50. The charge settles the fulfilled amount after the handler.
fulfill()marks the moment the service exists. If the handler fails after fulfilling, the charge still settles.- A fulfilled amount of
$0releases the reservation. - If a variable route never calls
fulfill(), nothing is charged and anerrorevent withFULFILLMENT_MISSINGis emitted. - Variable prices need the
authorizationflow and a rail withvariableAmount: x402 withuptoconfigured, L402, or KYAPay. The execution plan excludes incompatible rails, including MPP charge rails. Compatible rails still serve the route; only a route with no compatible rail is refused. Inspectgate.planortoll.explain(gate).
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.