๐Ÿšฆ

Rate limits & abuse

Stop scrapers, spam signups, and runaway bills before they hit.

Abuse rarely looks like a movie hack. It's a bot hammering your signup form, a scraper draining your API, or one user looping a button that bills you per call. Left unchecked it costs money, pollutes your metrics, and can take you down โ€” so cap things before they hit.

The three abuse shapes you'll actually see

  • Spam signups. Bots create thousands of fake accounts, wreck your DAU/user numbers, and burn your email-sending quota.
  • Scraping & brute force. Someone scripts your public endpoints or guesses passwords at scale.
  • Runaway cost. Any route that calls a paid API (LLMs, SMS, image generation) is a money faucet. One abusive user โ€” or your own retry loop โ€” can run up a real bill overnight.

Rate limiting: the default defense

Put a limit on every endpoint that does real work, keyed by user id (when authed) and by IP (when not).

  • Use a sliding-window or token-bucket limiter. @upstash/ratelimit with Redis is the common serverless-friendly pick; many platforms also offer edge rate limiting (e.g. Vercel) you can enable without code.
  • Apply tighter limits to expensive routes (LLM calls, exports) than to cheap reads.
  • Return HTTP 429 with a Retry-After header so well-behaved clients back off.
const { success } = await ratelimit.limit(`gen:${userId}`);
if (!success) {
  return new Response("Slow down", { status: 429 });
}

(Note the literal ${ above is escaped in source.)

Stopping spam signups

  • Verify email before the account counts for anything. Most spam dies here.
  • Add an invisible check โ€” a honeypot field, or a turnstile/captcha (Cloudflare Turnstile, hCaptcha) on the signup form. Prefer ones that don't nag real users.
  • Block disposable-email domains for paid or trust-sensitive flows.
  • Watch for bursts: many signups from one IP or subnet in a short window is a signal to throttle.

This also protects your leaderboard integrity โ€” fake accounts inflating user counts is exactly the weak signal the product already distrusts.

Capping cost before it caps you

This is the one that wakes founders up at 3am. Defense in depth:

  1. Set hard spend caps at the provider. OpenAI and Anthropic both let you set a monthly usage limit and billing alerts. Do it on day one.
  2. Budget alerts on your cloud (Vercel, Supabase, AWS) so a runaway gets an email, not a surprise invoice.
  3. Per-user quotas in your own DB โ€” e.g. N expensive actions per day on the free tier โ€” checked before you make the paid call.
  4. Idempotency & no blind retries. A retry loop on a failing paid endpoint can 10x your bill. Cap retries and dedupe with an idempotency key.

Don't forget the boring layers

  • Validate input size. Reject oversized payloads and absurd pagination (limit=1000000) before they hit the DB.
  • Lock down public write endpoints โ€” anything unauthenticated that writes data is a magnet.
  • Watch your metrics for anomalies. A sudden spike in a single endpoint is usually abuse, not virality.

The checklist

  • Rate limit on every meaningful endpoint, keyed by user + IP, returning 429.
  • Email verification + a quiet captcha/honeypot on signup.
  • Hard spend caps and billing alerts set at every paid provider.
  • Cloud budget alerts enabled.
  • Per-user daily quotas on expensive actions, checked before the paid call.
  • Retries capped; input size and pagination validated.
Building or growing an app?

I'm building ProveMyApp so founders like you can connect your apps, track their growth with verified metrics, and build alongside other founders instead of doing it alone.

Explore the library

More guides and playbooks to grow your app