VeveeBlog · 8 min read
Blog · 8 min read

How to stop free-tier abuse without killing signups

The usage graph spikes at 3:41am: four thousand free generations in an hour, all from disposable emails. Your first instinct is a credit-card wall. That instinct will cost you more than the abuse does.

Last updated: 2026-06-09

Every free generation costs you real money

The usage graph spikes at 3:41am on a Tuesday: four thousand image generations in fifty minutes, every one of them from a free account, every account registered to an address like qx81@tempmail.lol. For a normal SaaS this would be a curiosity - some bandwidth, some database rows, shrug. For an AI app it is a line item, because every free action has a real marginal cost: tokens billed by your provider, GPU seconds on your render queue, video minutes that cost more than the coffee you were drinking when you set the free quota. A farmed free tier does not degrade your service, it debits your bank account directly. And yet the free tier is also your only acquisition channel - the entire reason it exists is that a stranger can try the product in thirty seconds without talking to anyone, and every ounce of friction you add at signup costs you some of the legitimate users the tier was built for. So the goal is not to make signup harder. The goal is to raise the cost of abuse while leaving the cost of signup at zero, and those are different projects.

Know which abuser you're fighting

Free-tier abuse is three different problems wearing one name, and they need different countermeasures. Tier one is casual overuse: one human, one account, several browser tabs firing parallel requests that sail past your daily cap. They are not malicious - your limit just is not real, and they found out by accident. Tier two is multi-accounting: one human, ten Gmail aliases, cycling through accounts when each one hits the wall. Annoying, bounded, and cheap to dampen. Tier three is industrial farming: scripts, disposable-email APIs, your client traffic reverse-engineered, hundreds of accounts created programmatically to extract free inference at scale - usually to resell it. The expensive mistake most teams make is deploying a wall sized for tier three - credit card required, CAPTCHA everywhere - that only ever stops tier one, because the industrial farmer has stolen card numbers and CAPTCHA-solving services, while your legitimate signup does not. Match the countermeasure to the tier, and start at the bottom, because the bottom tier is usually most of the volume.

Layer 0: make the limit you advertise actually real

Before you buy a fraud-detection product, check whether your cap is enforced at all, because a striking share of "abuse" is just the limit not being real. Two classic holes. First, client-side-only gating: the React app hides the generate button after ten uses, but the API endpoint behind it checks nothing - anyone with the network tab open has unlimited access. Second, the check-then-act race: your server reads the counter, sees 9 of 10, calls the model, then increments - and twenty parallel requests all read 9 of 10 before any of them writes, so a single user with a for-loop burns 30 generations against a cap of 10. The fix is to make the check and the increment one atomic operation on the server, so that under any concurrency exactly the advertised number of requests succeeds and the rest get a clean 429. If your cap holds atomically server-side, tier one is already solved - not discouraged, solved - and you have spent zero friction doing it.

// Check and increment in ONE statement - no read-then-write gap
const result = await db.run(
  'UPDATE counters SET used = used + ? ' +
    'WHERE user_id = ? AND period_start = ? AND used + ? <= quota',
  [qty, userId, periodStart, qty],
);

if (result.rowsAffected === 0) {
  // Cap reached (or no counter row) - nothing was incremented
  return json({ error: 'limit_reached' }, { status: 429 });
}

// Quota is held; safe to call the model now
const image = await generateImage(prompt);

Let anonymous visitors try - on a smaller meter

Here is the counterintuitive move: instead of forcing signup before the first generation, remove the account requirement entirely and give anonymous visitors a much tighter quota keyed on a device or session identifier - three generations instead of twenty-five, cheap models only. The user id in your metering layer is just an opaque string you choose, so a cookie-backed session id like anon_f3a91c works exactly like a real user id: same counters, same enforcement, just attached to a different, stingier plan. When the visitor wants the full free tier, that is the moment you ask for a verified email - and now signup is not a wall in front of the product, it is an upgrade from something they already tried and liked. This converts better than signup-first, because the product sells itself before you ask for anything, and it simultaneously caps drive-by abuse: the person who clears cookies to reset their anonymous quota is grinding through three cheap generations per reset, which costs them more effort than it costs you money. You have made the free sample smaller without making the door narrower.

Filter email quality, not email ownership

Verified email is your tier-two checkpoint, and it works only if the email is worth something - which a disposable address is not. Three cheap checks, none of which a legitimate user will ever notice. Block disposable-email domains at signup: the open-source blocklists cover tens of thousands of burner domains and the lookup is a hash-set membership test, microseconds per signup. Normalize plus-aliases and dot-tricks before uniqueness checks: jane+1@gmail.com, jane+2@gmail.com and j.a.n.e@gmail.com are one mailbox, so treat them as one account instead of letting one Gmail address mint unlimited free tiers. And rate-limit account creation per IP - five signups per hour from one address is a human household at its absolute peak; fifty is a script. The point is the asymmetry: a real user signs up once, with their real address, from their own connection, and sails through all three checks without seeing them. The multi-accounter now needs real, distinct mailboxes, which converts a thirty-second loop into a manual chore. You have not added friction - you have added cost, and only for the person extracting value.

The friction ladder: gate the expensive 10%

Now arrange those layers into a ladder where the friction a user faces scales with the value they can extract, instead of being a flat wall at the door. Rung one: anonymous, no account, tiny quota, cheapest models - cost of abuse near zero because the value extracted is near zero. Rung two: verified non-disposable email, the normal free tier - enough quota to evaluate the product properly for weeks. Rung three, and only rung three, carries heavy verification: card-on-file or phone verification, reserved for the features where your marginal cost actually hurts - video generation, the premium image model, long-context calls. The arithmetic that makes this work is that abuse value is concentrated: the expensive features are typically a tenth of your catalog but the large majority of your potential burn, so gating that 10% behind a card removes most of the farming incentive while the 90% of users who came for the everyday features never see a card form at all. A farmer who wants your cheap model at free-tier volume is welcome to grind for scraps; the thing worth industrializing is locked behind exactly the check that industrial operations find expensive to fake at scale.

For industrial farming, detection beats prevention

You will not pre-block tier three, because tier three is built to pass every gate you put up - real-looking emails, residential proxies, human-solved CAPTCHAs. What farming cannot do is look like organic usage, and that is where per-user usage analytics stop being a reporting feature and become a security tool. Farmed cohorts have a signature: a batch of accounts created within the same hour, each with an identical usage shape, each hitting exactly 100% of the daily cap, every day, at machine-regular intervals - real users are bursty, partial, and irregular. If your events carry metadata like model and feature, the signal gets sharper still, because farms harvest the expensive path exclusively: fifty accounts that have only ever called the premium model and never opened the editor are not fifty enthusiasts. Make it a weekly ritual to sort free users by provider cost and skim the top twenty - the farms sit at the top, clustered and uniform, and a same-day registration cohort with matching consumption curves is as close to a confession as you will get. You cannot block what you cannot see; most teams being farmed simply have no per-user view, so the burn hides inside an aggregate bill.

What not to do

Every one of these is a real countermeasure someone ships in the angry week after discovering abuse, and every one of them costs more in lost signups or support load than the abuse it prevents.

  • Credit-card-wall the entire free tier. It kills your acquisition funnel to stop tier one and tier two - the tiers already handled by atomic enforcement and email hygiene - while tier three passes it with stolen or prepaid cards.
  • CAPTCHA on every action. A CAPTCHA at a suspicious signup is fine; a CAPTCHA per generation taxes every legitimate user on every use, and farms route around it with solving services for fractions of a cent.
  • Shadow limits the user cannot see. Silently degrading or blocking users past an unpublished threshold feels clever and generates a support ticket per occurrence - "your app is broken" - plus public posts when they compare notes. Advertise the limit and return a clear limit_reached.
  • Banning by IP address. Offices, universities, and mobile carriers put thousands of people behind one NAT address; one abuser gets you a thousand false positives. Rate-limit signups per IP, but never hard-ban the address.

The enforcement layer is the boring part - don't build it

Everything above reduces to one stack: per-user counters enforced race-safely, an anonymous plan and a verified plan keyed on ids you choose, reserve/commit semantics for the expensive paths, and per-user cost analytics that make farming visible before the invoice does. That stack is exactly what Vevee provides as a drop-in SDK - can, track, reserve/commit/release, plans with limit groups, per-user usage analytics - with a free tier to start, so you can spend the week on your product instead of on your counters table.

More from the blog

engineering · 9 min

The race condition in "if (usage < limit)" that is costing your AI app money

A user at nine of ten images opens six tabs and clicks generate in all of them. Every tab passes your limit check, every tab gets an image, and you pay for all six. The bug is one read-then-write - and your unit tests will never catch it.

engineering · 9 min

How to build a credits system for your AI app (ledger design, rollover, refunds)

A user emails: "I had 40 credits this morning and now I have 12, and I only made one image." If your balance is a column you mutated, you cannot answer that email. Here is the ledger design that can.

thinking · 7 min

Stripe metered billing for AI apps: what it solves and what it does not

You are sketching the pricing page for your AI app and every plan ends in "per generation" - because your costs arrive per generation. Stripe meters the charging half beautifully. The question is what happens at request time.

thinking · 8 min

Helicone is in maintenance mode. An honest map of where to go

Somewhere in your codebase a base URL points at Helicone, and it has been quietly doing four jobs at once. The team got acquired - good for them - but now you have to replace each job separately, and the one everyone forgets is the one your users will exploit.

thinking · 7 min

Tokens, credits, or requests: choosing the unit you meter (and price)

The pricing doc has three columns: $9, $19, $49. The prices took twenty minutes. The row above them - what a user actually gets for the money - has been blank for a week. That blank row is the real decision.

engineering · 3 min

I rewrote my cancel flow with one LLM call. It argues better than I do.

The cancel flow is where SaaS revenue goes to die politely. "Are you sure? You'll lose access to Premium features" has never changed a single mind - but reminding a user of their own 312 generations this month is an argument.

engineering · 3 min

The trial-ending email everyone sends is the same email. Here's the one that converts

"Your trial ends in 3 days! Upgrade now to keep access." You've received a hundred of these. You've deleted a hundred of these. The email fails because it's about your product, not about the user's trial.

engineering · 3 min

Stop making users do math on your pricing page: recommend their plan from their own usage

Every pricing page asks the user to solve an estimation problem: "How many credits will I need per month?" Nobody knows. But for any user who has actually used your product, their usage history IS the answer - here is how to put it on the pricing page.

engineering · 3 min

Win-back emails fail because they're written for "users." Write them for the one user instead.

Every dormant-user campaign in history: "We miss you! Here's what's new." Open rate: pity clicks. The email fails because it's about your changelog - and the user left because of something in their experience.

engineering · 3 min

Spotify Wrapped is a growth loop, not a year-end gimmick. Ship one for your AI app in an afternoon.

Spotify Wrapped works because people love seeing their own behavior reflected back as a story. Every AI app with usage data can run that loop monthly - and almost none do, because turning usage rows into narrative used to be a content problem. It is now a schema problem.

engineering · 3 min

"AI-personalized copy converts better" - prove it or delete it. Here is the 40-line A/B harness

Half of the "we added AI personalization and conversions went up 40%" posts have no control group. The other half measured clicks, not revenue. If LLM-generated copy is going in front of your paywall, you owe yourself a real experiment - and the harness is tiny.

thinking · 3 min

AI personalization without the creepy part: opt-out as a first-class return value

Users have learned that "personalized for you" means "we mined everything you ever did" - and the backlash is rational. When I added LLM-generated personalized copy to my app, the part I sweated was not the generation. It was making declining it a real, respected choice.

engineering · 3 min

I stopped walking into demo calls blind: every lead now comes with a usage brief written 10 seconds before the call

Founder-led sales has one structural weakness: you have no time to prep. If the lead has touched your product, their usage history is the best discovery call you'll never have to run - here's how I turn it into a one-page brief, automatically, before every call.

thinking · 4 min

The "founder email" converts like crazy and scales like garbage. Here is the middle path.

A personal email from the founder converts trials at a rate no automated sequence touches - and at 50 signups a week it stops scaling. The middle path: drafts generated from each user's real usage, that you read, edit, and send yourself.

engineering · 3 min

Churn doesn't announce itself. My Monday Slack digest does it instead.

Every founder finds out about churn the same way: the cancellation email. By then the user has been gone for weeks - the decision happened earlier, quietly, in their usage. So I made the metering tables write me a memo.

engineering · 3 min

Half of every support ticket is asking what the user already did. Attach the answer instead.

The ticket says "it's not working" - and the next twenty minutes go to figuring out who this user is, what plan they're on, and whether "it" is a bug, a quota, or a misunderstanding. The actual fix usually takes two. All of that context lives in your usage data; here is how to attach it to every ticket automatically.

thinking · 4 min

Your users are telling you your roadmap, in writing, every day. It's in your prompt logs

Founders pay for interviews and beg for survey responses to learn what people are actually trying to do. Meanwhile, your users type their intent into your product, in their own words, hundreds of times a day - and nobody performs for a prompt box.

engineering · 5 min

How to reset usage limits when a subscription renews

A weekly plan, used twice. First week: ten generations. Second week: zero, because the counter never reset. This is the cron-job mistake - and the fix is one field on one call.

engineering · 6 min

How to manage subscription renewals: aligning Vevee with Stripe

A user signs up on Jan 15. Stripe charges them on the 15th of every month. Your metering layer resets on the 1st. Two clocks. One angry support ticket per cycle.

engineering · 5 min

Stop hardcoding your pricing page - render it from your metering layer

Every B2B SaaS I have shipped repeats the same mistake: plans live in two places - the dashboard that enforces them, and a const PLANS = [...] on the marketing site. They drift within a quarter.

thinking · 4 min

Meter AI by user, not by account - your margin depends on it

A few users will cost you 100x what your median user costs. If you only meter at the account level, you will not see them coming until your gross margin is gone.

engineering · 5 min

reserve / commit / release: the only correct way to enforce AI quotas

Every team I have seen build per-user AI metering has shipped a version of canUse → call OpenAI → track. It looks correct in single-threaded tests. It is broken in production.

thinking · 4 min

Why Stripe Billing is not enough for AI products

Stripe is excellent at one thing: turning usage into invoices. AI products need three other things, and Stripe does not do any of them.

engineering · 6 min

Dynamic onboarding: a different first step for every user

A teacher and a student sign up the same minute. The teacher wants to build a quiz; the student wants to summarize a lecture. Your onboarding shows them both the same five-step checklist. One of them bounces.

thinking · 6 min

Paywall copy that rewrites itself for every user

Your paywall says "Upgrade to Pro for unlimited generations." A teacher reads it and shrugs. A student on a budget reads it and closes the tab. The same words, two lost conversions - because the words were written for nobody in particular.

engineering · 5 min

Add usage limits to your AI app in 10 minutes (no backend required)

You shipped an AI feature on Friday. By Monday one user had burned $212 of OpenAI credit on your free tier. The fix is not a TODO comment that says "add rate limiting" - it is two method calls.

engineering · 5 min

Meter LLM tokens, not requests - your flat per-request limit is lying

Two users, both at 100 requests. One sent tweets, the other sent novels. Your cost for them differed by 400x. Your limit treated them identically - and your margins noticed.

engineering · 7 min

The upgrade nudge that writes itself: convert free users before they hit the wall

By the time a user hits your paywall, they are blocked, annoyed, and halfway to a competitor’s signup page. The best moment to make the pitch was three days earlier - when they were winning. Here is how to catch it, automatically, for every user at once.

engineering · 5 min

One event, two limits: gate your premium model without forking your code

The premium model launch was going great until you looked at the bill: free users had figured out the good model and were living on it. You need a sub-limit. You do not need a second code path.

engineering · 5 min

Test mode: break your pricing in the sandbox, not on your customers

You changed the free tier from 10 to 25 generations and somehow locked out every Pro user for an hour. Nobody tested it, because testing it meant tracking fake events into production analytics. There is a mode for this.

engineering · 5 min

The support ticket that solves itself: log the prompt behind every AI event

A user says your AI feature "broke" on Tuesday. You have a charge for the call, a timestamp, and no idea what they asked or what the model said. The evidence existed for exactly one request - the one you didn’t log.

engineering · 5 min

Your funnel has one broken step. Find it without writing a single SQL query.

A hundred people saw your paywall this week. Three upgraded. Is that a copy problem, a price problem, or did ninety of them never generate anything worth paying for? You cannot fix what you cannot locate.

engineering · 5 min

One user, three ghosts: fix your funnel with identify()

Your funnel says signup conversion is 4%. It is actually 11%. The missing users didn’t bounce - they came back on another tab and got counted as someone new. Every number downstream of that split is wrong.

thinking · 5 min

The usage bar that sells the upgrade (build it with a public key in an afternoon)

An invisible limit feels like a trap. A visible one feels like a fuel gauge. Same quota, same plan, same user - and a measurably different reaction when the wall finally arrives.

engineering · 6 min

How to cancel a subscription without burning the bridge (or your data)

The user cancels. Do they drop to a free tier, or lose access when the paid month runs out? Those are different products, different SDK calls, and different mistakes when you get them wrong.