SDK reference
@tollgate/sdk
TypeScript, MIT, zero runtime dependencies — Web Crypto and fetch only. Runs on Cloudflare Workers, Node 18+, Deno and Bun.
Tollgate
new Tollgate({ store: UsageStore, // required — D1Store | MemoryStore | yours freeCredits: 200, // signup grant defaultCost: 1, // per tools/call toolCosts: { overview: 0, heavy_tool: 5 }, methodCosts: { "resources/read": 5 }, // extra metered methods rpmLimit: 120, // per key per minute; 0 disables signupUrl: "…", // shown in -31401 errors checkoutUrl: "…", // shown in -31402 errors pricingHint: "$9 per 1,000 calls", })
Methods
| method | what it does |
|---|---|
| protect(request, next) | The middleware. Parses, authenticates, meters; calls next({ request, message, ctx }) on success, returns ready-made JSON-RPC errors otherwise. Non-POST and notifications pass through. |
| gate(request) | Lower-level protect: returns { kind: "pass", message, ctx } or { kind: "respond", response } for servers that route themselves. |
| signup(email) | Account + key + free credits. Returns the raw key once; only the SHA-256 hash is stored. |
| verifyKey(rawKey) | Resolve a key for REST endpoints → { account, key, balance } or null. |
| rotateKey(accountId) | Revokes all keys, issues a replacement (one transaction). |
| usage(accountId, days) | Totals, per-day series and per-tool counts for dashboards. |
| costOf(message) | What a message would cost (null = unmetered). Useful for previews. |
CallContext
Passed to your handler for metered calls: { account, keyId, balance, charged } —
balance is post-charge, so you can surface "credits remaining" in your results'
_meta the way the hosted server does.
Stores
import { D1Store, MemoryStore } from "@tollgate/sdk"; new D1Store(env.DB) // production: Cloudflare D1 new MemoryStore() // tests, toys; dies with the process
Apply the D1 schema once — tables are prefixed tollgate_ so they can live inside an
existing application database:
$ wrangler d1 execute YOUR_DB --file=node_modules/@tollgate/sdk/schema.sql
Custom backend? Implement the UsageStore interface (10 methods). Three rules carry all the
correctness: charge() must be atomic (single conditional UPDATE — never overdraws),
addCredits() must be idempotent on externalRef (webhooks retry), and
rateLimit() must increment-and-check in one step.
Keys
| function | notes |
|---|---|
| issueKey(accountId, label?) | tg_ + 32 url-safe chars; returns raw key, hash and display prefix. |
| hashKey(rawKey) | SHA-256 hex — the only thing you store. |
| keyFromHeaders(headers) | Reads Authorization: Bearer or X-Api-Key. |
| looksLikeKey(s) | Shape check before hitting the store. |
Metering model
- Free methods — anything not metered (
initialize,tools/list,ping, …) passes anonymously. A present-but-invalid key still fails fast, on purpose. - Zero-cost tools — callable without a key; they are your shop window.
- Charges — free credits are spent before paid ones. The usage log is observability; the balance is the source of truth.
- Notifications — never charged, never answered (per JSON-RPC).
- Batches — rejected with -32600; MCP removed batching in 2025-06-18 and unambiguous metering is worth more than legacy batch support.
Payouts to other authors (phase 2)
The purchase ledger stores provider, amount and external ref per purchase, and
billing/connect.ts defines the PayoutsDriver seam for Stripe Connect
(onboard author → transfer share minus platform fee). Deliberately not implemented in MVP;
deliberately impossible to paint into a corner.