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

Add prepaid credits

Let callers pay from a prepaid balance. Credits are reserved before the handler runs, then committed or released.

Public Beta · early access

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:

  1. The price is reserved on the balance.
  2. The handler runs.
  3. 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.

On this page