Every call,
paid for.
Tollstile is open-source payment middleware for APIs and MCP tools. Price a route in one line, accept any 402 protocol, and keep every receipt in your own database.
$ npx create-tollstileimport { createTollstile } from "tollstile";
import { tollstile } from "@tollstile/hono";
import { x402 } from "@tollstile/x402";
import { mpp } from "@tollstile/mpp";
import { postgresLedger } from "@tollstile/postgres";
const toll = createTollstile({
rails: [x402(), mpp()],
ledger: postgresLedger(db),
});
app.post(
"/v1/generate",
tollstile(toll.price("$0.04")),
async (c) => c.json(await generate(c)),
);The price is the only line your route gains.
- gate
- closed
- ledger state
- challenged
Challenge. No payment yet. Tollstile answers with every rail you accept.
HTTP/1.1 402 Payment Required { "price": "$0.04", "rails": ["x402", "mpp"]}Simplified exchange. Real challenges follow each rail’s specification.
Seven parts, one gate.
Protocols decide how a payment is proven. Everything around that — who pays, when money moves, what happens when your handler fails — is what every merchant ends up building. Tollstile is that part.
- 01
Price
A signed quote fixes it. Never the client.
toll.price("$0.04") - 02
Access
Decide who has to pay at all.
access: [subscriber(), …] - 03
Verify
Check the proof on any rail you accept — once.
rails: [x402(), mpp()] - 04
Settle
Choose when money moves, out loud.
flow: "authorization" - 05
Fulfill
Mark the moment the service actually exists.
payment.fulfill() - 06
Refund
Handler failed? Release or refund. Never twice.
automatic - 07
Record
Every transition, in your own database.
ledger: postgresLedger(db)
The whole policy fits next to the handler.
import {
createTollstile, subscriber, credits, payPerCall, limit,
} from "tollstile";
import { x402 } from "@tollstile/x402";
import { mpp } from "@tollstile/mpp";
import { postgresLedger } from "@tollstile/postgres";
const toll = createTollstile({
rails: [x402(), mpp()],
ledger: postgresLedger(db),
});
server.tool(
"generate_image",
toll.price("$0.04", {
access: [
subscriber({ active }),
credits({ balance }),
payPerCall(),
],
require: [limit({ spendPerDay: "$20" })],
}),
async (input, { payment }) => generateImage(input),
);- L9
Rails
How agents can pay. Add or remove one without touching a handler.
- L10
Ledger
Where the truth lives. Your database, your schema.
- L15
Price
Declared by the server, checked against every proof.
- L16
Access
Tried in order. Subscribers pass, credits draw down, the rest pay per call.
- L21
Require
Conditions every request must meet, like a daily spend cap per payer.
Rails, now and next.
Every rail declares what it can do. A route that needs a capability its rail lacks refuses to start.
Full roadmap| Rail | Pays with | Use | Target |
|---|---|---|---|
| test | Local rail | 402 → 200 without a wallet | v0.1 |
| x402 exact | Stablecoin | Fixed price per call | v0.1 |
| mpp charge | Stablecoin · card | One payment per call | v0.1 |
| x402 upto | Stablecoin | Authorize a ceiling, settle what ran | v0.1 |
| mpp session | Stablecoin · card | Many calls, one authorization | v0.1 |
| l402 | Lightning | Bitcoin over Lightning | v0.1 |
| kyapay | Stablecoin | Agent identity with payment | v0.1 |
The fine print, printed large.
Tollstile guarantees
- A priced handler never runs without granted access or a verified payment.
- Retries, replays, and crash recovery never settle or refund twice.
- Every transition is written to your ledger before it is acknowledged.
- When an outcome is unclear, the payment is marked unknown and reconciled.
Tollstile does not promise
- Your handler running exactly once.
- The response reaching the client.
- Recovering costs your handler already incurred.
- How a rail’s provider behaves.
Stripe moves the money.
Tollstile works the gate.
Use Stripe, Coinbase, or any provider behind a rail. Tollstile sits in your app, decides who gets through, and keeps the record. It charges nothing and never holds funds.
How Tollstile compares