Add prepaid credits
Let callers pay from a prepaid balance. Credits are reserved before the handler runs, then committed or released.
Use credits() when callers top up a balance and each call draws from it.
import { credits, memoryBalance, payPerCall } from "tollstile";
import { tollstile } from "@tollstile/hono";
const balance = memoryBalance({ acct_1: "$5.00" }); // implement Balance on your database in production
app.post(
"/v1/generate",
tollstile(
toll.price("$0.02", { access: [credits({ balance }), payPerCall()] }),
{ principal: (c) => (c.get("user") ? { id: c.get("user").id } : null) },
),
generate,
);- Signed-in callers with enough credit pass; anyone else is asked to pay per call.
- The account defaults to the authenticated principal's id. Pass
account: (context) => …to choose another.
Reserve, commit, release
A charge is recorded in the ledger before the balance is touched:
- The price is reserved on the balance.
- The handler runs.
- On success the reservation is committed; on failure it is released.
If the process dies in between, reconciliation asks the balance what happened and commits or releases — credits are never lost or spent twice.
Implement Balance on your database
import type { Balance } from "tollstile";
export const balance: Balance = {
async reserve(account, amount, key) {
// In one transaction: if a reservation with `key` exists, return "reserved".
// Otherwise, if available >= amount, subtract and insert (key, account, amount, "reserved").
return "reserved"; // or "insufficient"
},
async commit(key) { /* reserved → committed */ },
async release(key) { /* reserved → released, and add the amount back */ },
async status(key) { return "committed"; /* reserved | committed | released | none */ },
};Every method must be idempotent by key.
Retries
Send Idempotency-Key on the first request and retries. Credit charges use the policy account as payer scope: a completed charge returns 409 already_paid without another balance reservation or handler call. Without a key, each admitted request spends credits again. Released attempts can run again. See Idempotency.
Sell an MCP server to people
Charge for MCP tools today, without waiting for clients to learn how to pay — identify the user, draw on what they bought, and send them somewhere to top up.
Let subscribers through without paying
Give active subscribers free access to paid routes while everyone else pays per call.