Short answer: SaaS stack: Next.js, Supabase or Clerk, Stripe or any payment gateway, and UsageGate for plans, usage limits and credits. Don't build that part yourself. Spend a credit with an atomic call before each model call, and let Stripe upgrades raise the allowance.
The stack
| Layer | Use |
|---|---|
| App | Next.js (App Router), streaming responses from route handlers |
| Model | Anthropic (Claude), OpenAI, or any model API |
| Database & auth | Supabase, or Clerk for auth |
| Credits & plans | UsageGate: monthly credits per plan, spent atomically before each call |
| Payments | Stripe, or Paddle / Lemon Squeezy |
| Hosting | Vercel |
1. Decide what a credit is worth
Pick one unit users understand: one generation, one chat message, one image. Work out what it costs you at the model provider, then set each plan's monthly credits so a paid plan earns well above its cost. For example, Free gets 10 credits and Pro ($19/month) gets 500. You can change these numbers later without a deploy.
2. Give new users the free plan
// After sign-up (or on every login; it is safe to repeat):
await gate.grantPlan(user.id, "plan_free"); // e.g. 10 credits a month3. Spend a credit before every model call
This is the part that protects your bill. Spend first, then call the model. If you check after the call, a user can fire ten requests at once on their last credit and you pay for all ten.
import { GateClient } from "@usagegate/sdk";
const gate = new GateClient(process.env.USAGEGATE_KEY!);
export async function POST(req: Request) {
const user = await currentUser(); // Supabase or Clerk
if (!user) return new Response("Sign in", { status: 401 });
// 1. Spend the credit first. consume() is atomic: two parallel
// requests can never both use the last credit.
const spent = await gate.consume(user.id, "ai_credits", 1);
if (!spent.success) {
return Response.json({ error: "Out of credits", upgrade: "/pricing" }, { status: 402 });
}
// 2. Only then call the model you pay for.
const { prompt } = await req.json();
const answer = await callModel(prompt); // Anthropic, OpenAI, ...
return Response.json({ answer, creditsLeft: spent.remaining });
}4. Upgrades with Stripe
Put your Stripe Price id on the paid plan in UsageGate, then send the user to Checkout with their id. When they pay, their monthly credits go up on the next request; when they cancel, they drop back to Free without getting spent credits back.
// Upgrade: tag the Stripe subscription with the same user id.
await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: process.env.STRIPE_PRICE_PRO!, quantity: 1 }],
subscription_data: { metadata: { end_user_id: user.id } },
success_url, cancel_url,
});5. Keep the bill under control
- Require sign-in for anything that calls the model.
- Keep the free allowance small, and raise it once you see conversion.
- Add a rate limit on the route for bursts.
- Charge long outputs more: consume more units for an expensive call, or use a separate feature like
ai_tokens.
Start from a working app
The Next.js SaaS starter has all of this wired: Supabase sign-in, 10 free credits a month, a Generate button that stops at zero, and Stripe upgrades. Replace the fake generation with your model call.
FAQ
How do I add credits to an AI app?
Give each plan a monthly number of credits (Free: 10, Pro: 500), spend one credit before each model call, and refuse the call when the user has none left. With UsageGate you set the amounts in a plan table and call consume(userId, "ai_credits", 1) before the model call; it refills each month on its own.
Should I check credits before or after calling the model?
Before. Spend the credit first with an atomic call (consume), then call the model. Checking after the call lets a user run many requests in parallel on their last credit, and you pay for all of them.
How many credits should a free plan get?
Enough to reach the moment the product is useful, and few enough that the model bill stays small. Work out your cost per call, multiply by the free credits and your expected free users, and keep it a small share of what a paid plan earns. You can change the amount any time without a deploy.
How do I stop free users from abusing my AI app?
Require sign-in, give a small monthly credit allowance, spend credits atomically before each call, and add a rate limit on the route. UsageGate handles the allowance and the atomic spend; your auth provider handles sign-in.
Can I charge per token instead of per request?
Yes. Spend the number of units the call actually used: estimate before the call and consume that amount, or use a separate feature such as "ai_tokens" with a larger monthly allowance.

