Quickstart
Add pay-per-call pricing to an API in five minutes with the test rail. No wallet or account.
Or try it without installing anything
demo.tollstile.com is a paid API running this code on Cloudflare Workers: meet a 402, pay it with the test rail, and watch the charge land in the ledger. The same demo is a paid MCP tool at https://demo.tollstile.com/mcp, where one tool is charged only after a person approves the amount — see Consent.
This guide prices a Next.js route handler with the test rail and memory ledger, so there is nothing to sign up for. The same two steps work on Express, Hono, any fetch handler, and MCP tools.
1. Install
npm install tollstile @tollstile/nextOr start from a template: npx create-tollstile my-paid-api.
2. Price a route
import { createTollstile, memoryLedger, testRail } from "tollstile";
export const toll = createTollstile({
rails: [testRail()],
ledger: memoryLedger(),
});import { paid } from "@tollstile/next";
import { toll } from "@/lib/toll";
export const GET = paid(toll.price("$0.01"), () => {
return Response.json({ forecast: "clear" });
});That is the whole change: the handler you already had, with a price in front of it.
3. Call it without paying
curl -i localhost:3000/weatherHTTP/1.1 402 Payment Required
{
"error": { "code": "payment_required", "retryable": true, "action": "pay", "message": "Payment required.", "detail": null },
"price": "$0.01",
"quote": "eyJ2IjoxLCJpZCI6…",
"accepts": [{ "rail": "test", "amount": "10000", "flow": "authorization", … }]
}The quote is a signed record of what the server offered. It is never stored.
4. Pay with the quote
curl -i -H "Payment: test quote=eyJ2IjoxLCJpZCI6…" localhost:3000/weatherHTTP/1.1 200 OK
payment-receipt: test_settlement_chg_…
{ "forecast": "clear" }The ledger now holds one authorization and one charge in settled/completed. For fixed prices, Payment: test without a quote also works.
5. Switch to a real rail
Replace testRail() with live rails and memoryLedger() with a database ledger — on serverless, a database ledger and one shared secret across instances. The route does not change. The tutorials walk through it:
- Add a paid route to Next.js
- Monetize an API with x402
- Accept MPP payments
- Charge for MCP tool calls
- Charge per call on Cloudflare Workers
Next
Retry the same operation
curl -i -H 'Payment: test' -H 'Idempotency-Key: weather-1' localhost:3000/weather
curl -i -H 'Payment: test' -H 'Idempotency-Key: weather-1' localhost:3000/weatherThe first succeeds; the second returns 409 already_paid. The default test payer is stable even though Payment: test creates a fresh proof. See Idempotency.