Building Apps with AILesson 74 of 87

Contents

Building Apps with AI

Lesson 74 of 87. Sign in to keep your place.

Module 8 · The security holes AI code has by default

Rate limits, and the sign-up flood

8 min read

The key decides how tight the limit can beWhat the key isHow tightBefore sign-inIP addressall you haveGeneroushundreds a minuteAfter sign-inThe accounta personTighttwenty calls a dayCarrier-grade NAT puts thousands of people behind one public address, so a tight per-address limitblocks a city while a bot with a pool of addresses sails through. Tightness is bought by knowing whois asking.
Carrier-grade NAT puts thousands of people behind one public address, so a tight per-address limit blocks a city while a bot with a pool of addresses sails through. Tightness is bought by knowing who is asking.

Figure

What arrives once the site is public

Within days of any site becoming reachable, automated traffic finds it: scanners probing for known paths, scripts creating accounts, scrapers pulling every page, and (if there is a route that costs money to serve) somebody calling that route in a loop. None of this is personal. It is the background weather of the internet, and a generated app is built as if it did not exist.

Three routes need protecting first: sign-up, anything that sends email or SMS, and anything that calls a model.

The sign-up flood

A registration form with no bot check will receive a thousand accounts overnight. Each one may trigger a welcome email, consume a free-tier seat, and, if there is a per-account AI allowance, get twenty free model calls. The cost lesson in the previous block warned that a thousand accounts at twenty calls each is twenty thousand calls.

The defence before sign-in is a bot check. Cloudflare Turnstile is free, does not show puzzles to most people, and takes fifteen minutes to wire: a widget on the form produces a token, and the sign-up route verifies the token with Cloudflare’s endpoint before creating the account. hCaptcha is the free alternative. Neither is perfect (paid solving services exist) and both remove the effortless case, which is most of the flood.

Beside it, the sending routes. Password reset, magic link, invite, contact form: each sends an email on request, and an unprotected one is a spam relay with your domain’s reputation attached. Bot check, plus a limit per address and per target email, plus a delay: a reset link is not needed within a second of the last one.

Limits, and where they apply

A rate limit is a counter with a window: at most N requests per key per period. The key is the whole design.

Before sign-in, the key is the address. It is all you have. Set it generously, for the reason in this lesson’s question: in much of the world, mobile carriers and offices put thousands of people behind a handful of public addresses through carrier-grade NAT. A limit of twenty a minute per address blocks a city’s worth of honest users on one network while a bot with a pool of addresses sails through. Per-address limits are for stopping the crude flood, so set them high (hundreds a minute) and rely on the bot check for the rest.

After sign-in, the key is the account. Now the limit can be tight, because it is the person and not the network. Twenty model calls a day, ten uploads an hour, whatever the feature warrants, counted in your own table as the cost lesson showed.

For the expensive route, both. An address limit to stop the loop and an account limit to stop the patient abuser.

Implementing one

On a serverless host, a counter has to live somewhere shared: a Redis-like store (Upstash has a free tier and a ready-made limiter library), a Durable Object on Cloudflare, or a row in your own database with an atomic increment. In-memory counters in a serverless function reset with every process and protect nothing.

The usual algorithm is a token bucket: each key has a bucket that refills at a fixed rate and holds a maximum; a request takes a token or is refused. It permits short bursts and enforces an average, which is how people actually behave. A limiter that fails (the store is unreachable) should fail open with a log line rather than refuse everyone; an outage of the limiter should not be an outage of the app.

Return 429 with a Retry-After header. Well-behaved clients and your own front end read it; the front end can say “too many requests, try again in a minute” instead of showing a generic error.

What a limit costs

Every limit refuses somebody honest eventually: the teacher importing forty students, the user on a shared network, the person who pressed the button ten times because the page was slow. Watch the 429 log for the first month. A key that hits the limit constantly is either a bot, in which case the limit is working, or a real pattern of use you did not anticipate, in which case the limit is the bug.

The scraper

Public pages will be scraped. This is not harmful in itself, and it can be expensive if each page costs a database query, which is where caching and static rendering, covered in the cloud course, earn their place. Block only the obviously abusive (thousands of pages a minute from one key) and let the rest read; a public page is public.

Every lesson here is open. Your progress is recorded once you pass the Building with AI exam.

Check your understanding

A per-IP limit of 20 requests a minute is added to every route. The next morning, users in one Indian city report the app refusing them constantly while they are barely using it. Why?

Pick the one you would defend. Your answer stays private.

Next lesson · 8 minUploads that run

Stuck on something?Ask Beatriz, your AI tutor

How to cite: Addaly (2026) ‘Rate limits, and the sign-up flood’, in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/rate-limits-and-the-sign-up-flood (Accessed: 13 October 2026).