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

Add pay-per-call pricing to an API

Charge AI agents and API clients a fixed price per request with HTTP 402, using Tollstile.

Public Beta · early access

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

toll.ts
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

server.ts
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 Required with price, a signed quote, and an offer per rail in accepts.
  • 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 400 or 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 usage

Checklist for production

  • Pass secret from 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.

On this page