Per-user rate limits and AI spend caps for Next.js + Supabase + Stripe
Copy-paste TypeScript and one Supabase migration, designed to help cap abuse and surprise bills. $19 (plus VAT where applicable). One-off, no monthly bill from the kit.
Get the Spend Caps Kit ($19 plus VAT where applicable)
What it does
The kit runs inside your app, on Upstash or on your own Supabase Postgres. It covers three places where a small app tends to get an unexpected bill: a route that anyone can hammer, an AI call with no per-user ceiling, and a checkout button that creates a new Stripe session on every refresh.
Rate limits (lib/ratelimit.ts). Five presets: anonymous IP, signed-in user, AI route, auth/OTP, and checkout. They run on Upstash's sliding window or a Supabase Postgres counter. Over the limit, the route returns 429 with Retry-After. If the limiter backend is down, it returns 503 by default; you can switch that to let traffic through.
AI spend cap (lib/spend-cap.ts). A dollar ledger per user per month, priced from each response's token usage, including cached tokens, cache writes, and streams. A stream cut off before its final usage is charged at least the up-front estimate. It warns at 80% and blocks the next AI call at 100%.
- The price table covers 16 current OpenAI and Anthropic models, last checked 17 Oct 2026.
- A model that isn't in the table is refused, not counted as $0.
- Each call reserves its estimated cost first, so parallel calls can't all slip under the cap at once.
Checkout guard (lib/checkout-guard.ts). For the same user and cart within 10 minutes, it hands back the open Stripe Checkout Session instead of creating another. It adds idempotency keys and a per-user limit, and dedupes webhooks on event.id with a pending/completed status.
SQL (sql/001_spend_caps_kit.sql). Four tables with RLS on, and two security definer functions with a pinned search_path that only your server's service role can call.
Also in the zip
- Example wiring:
proxy.ts(Next 16) ormiddleware.ts, an AI route, a checkout route, a webhook route, and a checkout button that disables itself. - A backstop checklist for provider-side limits in OpenAI, Anthropic, Vercel, Supabase, and Upstash, each marked "verify in your dashboard".
- A
check-abusereview as a Claude Code skill and a Cursor rule. It looks for unthrottled routes, AI calls without a user or cap, and checkout without idempotency. - A load test script that fires 50 requests at your route and passes when it sees 429s.
- A 15-minute setup guide and a "how it works and its limits" page.
How we tested it
- 51 automated tests pass on Node 20.19 (
npm run verify): 39 unit tests, 10 SQL tests that run the migration in PGlite (Postgres in WASM) and check grants and RLS, and 2 tests of the load script. - The unit tests cover limits, client IP sources, cost maths, reservations under 20 parallel calls, 50 cut-off streams against a $1 cap, checkout reuse over 50 clicks, and webhook dedupe.
- The example routes type-check against Next.js 16.4, Stripe 23, OpenAI 7.35, Upstash Ratelimit 2.2, and supabase-js 2.118.
What this doesn't do
- The Supabase backend is a fixed-window counter, so it can allow up to 2x the limit across a window boundary. Use the Upstash backend if that matters.
- The SQL was tested in PGlite with stub Supabase roles, not against a hosted Supabase project.
- No live provider calls were tested. The tests use fakes, not live OpenAI, Anthropic, Stripe, or Upstash accounts.
- The price table uses standard-tier prices. Batch, flex, fast mode, data-residency uplifts, and tool fees aren't included. Prices change: check the table before you rely on it.
- Rate limits and spend caps reduce the risk of surprise bills; they are not a replacement for the provider-side limits in the checklist.
Requirements
Node 20+, Next.js App Router, and Supabase. Upstash is optional. The kit has no runtime dependencies of its own; you install the SDKs you already use.
FAQ
- Is there a monthly fee?
- No. You pay $19 once, plus VAT where applicable. The kit runs in your app, on Upstash or your own Supabase Postgres.
- What happens if a user calls a model that isn't in the price table?
- The call is refused before it reaches the provider, rather than counted as $0. Add the model's prices to
PRICESinlib/spend-cap.tsto allow it. - What if a user fires several AI calls at once?
- Each call reserves its estimated cost before it runs, and a failed call releases its reservation. That's designed to stop parallel calls from all slipping under the cap together (tested with 20 parallel calls).
- Do I need Upstash?
- No. Without it, limits run on a Postgres counter in your Supabase project. That counter is fixed-window; Upstash gives you a sliding window.
- Has it been run against a live Supabase project or live provider accounts?
- Not yet. The SQL ran in PGlite and the provider calls are faked in tests. See "What this doesn't do".
- Do I get updates?
- Free updates within 1.x. Single-user commercial licence.
- Refunds?
- Digital download: by buying, you ask for immediate access and agree that your 14-day right to cancel ends once the download or access starts. Our voluntary 14-day "if it didn't help" refund guarantee still applies, and your rights for faulty content are not affected.