Add pay-per-call pricing to an API
Charge AI agents and API clients a fixed price per request with HTTP 402, using Tollstile.
Use this when you want each call to a route to cost a fixed amount, for example $0.05 per request.
1. Create one Tollstile instance
import { createTollstile, memoryLedger, testRail } from "tollstile";
export const toll = createTollstile({
rails: [testRail()], // replace with live rails in production
ledger: memoryLedger(), // replace with a database ledger in production
});Create it once per process and import it wherever routes are defined.
2. Wrap the route
import { Hono } from "hono";
import { tollstile } from "@tollstile/hono";
import { toll } from "./toll";
const app = new Hono();
app.get("/weather", tollstile(toll.price("$0.05")), (c) => c.json({ forecast: "clear" }));toll.price() validates the route against every rail where it is defined, so a misconfiguration fails at startup.
3. What callers see
- Without payment:
402 Payment Requiredwithprice, a signedquote, and an offer per rail inaccepts. - With a valid payment: your handler runs, the charge settles after it succeeds, and the response carries a receipt header.
- If your handler throws or answers
400or above: the reservation is released and nothing is charged.
4. Price several routes
app.get("/weather", tollstile(toll.price("$0.01")), weather);
app.post("/v1/generate", tollstile(toll.price("$0.05")), generate);
app.post("/v1/render", tollstile(toll.price(upTo("$0.50"))), render); // see Charge for usageChecklist for production
- Pass
secretfrom your secret store when using live rails. - Use a database ledger so charges survive restarts.
- Run
toll.reconcile()on a schedule. - Name routes with path parameters explicitly:
toll.price("$0.05", { resource: "GET /users/:id" }).
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.