golemkit
← All posts

July 13, 2026 · 5 min read

Guardrails for AI free tiers: shipping free credits without getting burned

The free tier of a normal SaaS costs you almost nothing — some rows in a database, a few requests. The free tier of an AI SaaS is different in kind: every free credit is a prepaid claim on your AI Gateway account. A signup bonus times a script that mints accounts is not a growth curve, it’s an invoice. You don’t need to be attacked by anyone sophisticated; you need one person with a loop and a disposable-email provider.

This boilerplate ships free credits anyway, because a free taste is how AI products convert. It just ships the guardrails in the same commit. Here’s the checklist, and where each item lives.

1. Make an account cost something

Email/password sign-in is blocked until the address is verified (requireEmailVerification: true). The signup bonus is granted at account creation, but an unverified account can’t sign in — so it can’t spend. That one flag turns “mint accounts in a loop” into “operate a real inbox per account”, which is exactly the cost the attacker was avoiding.

2. Rate limit by IP where there is no user yet

Per-user rate limits protect your AI routes, but signup abuse happens before there is a user. Two auth endpoints can be abused anonymously — account creation (mints credit bonuses) and magic-link requests (sends email on your Resend account). Both are limited to 10 per hour per client IP, keyed on the platform-set x-forwarded-for. It won’t stop a botnet; it stops the loop on a laptop, which is the attack you’ll actually get.

3. Keep the bonus a taste, not a meal

The signup bonus is 15 credits — a handful of chats and one image. The constant has a comment explaining the reasoning, because the next person to touch it will be tempted to add a zero for a launch promo. Whatever the number, price it as: worst case, every credit becomes real gateway spend.

4. Cap inputs, because post-hoc metering can’t collect debt

Usage is charged after the model call, from the tokens the model actually reported. That’s the honest way to meter — but it means a charge can only collect what’s left of the balance. Without input caps, one request with a huge prompt could cost you far more than the user’s remaining credits. So every entry point is size-capped before the model is called: 32,000 characters for text endpoints, 4 MB for PDF uploads, with 413 as the answer. The cap bounds the worst case a single call can cost you.

5. Pre-check before spending, lock while charging

Every metered route checks the balance and returns 402 before paying the model provider — the cheap query runs before the expensive call. The charge afterwards runs inside a transaction that locks the balance row (SELECT ... FOR UPDATE), so two concurrent requests serialize instead of both reading the same balance and driving it negative. Race conditions in billing aren’t theoretical; streaming endpoints finish concurrently all the time.

6. Keep a kill switch

When someone does slip through, you need an off button, not a database session. The admin panel can deactivate a user, and a single session-creation hook enforces it across every sign-in method — password, OAuth, magic link. Disabled accounts keep their rows and their ledger history; they just can’t mint a session anymore.

None of this is exotic. Each guard is a few lines in the module that owns it — auth, billing, ratelimit — and each one exists because of a specific, boring way a public AI demo gets expensive. The point of a boilerplate is that they’re all wired up before your first bill, not after it.