TL;DR: API rate limiting caps how many requests one user, IP, or key can send in a window. It stops credential stuffing, scraping, and surprise bills on routes that call paid APIs. Add it to login, password reset, and every paid route first. Most stacks need one small library and an afternoon. Then prove it works with a burst test — or check one endpoint now with our free API rate limit checker.
Here is the uncomfortable part. If an AI tool built your app, your endpoints almost certainly have no limits at all. Nothing stops a bot from trying 10,000 passwords against your login route tonight. Nothing caps a script that hammers the route that calls your AI model, on your bill.
This guide keeps it practical. What rate limiting stops. Where to add it. The exact setup for each common stack. And how to test it the way an attacker would.
What API rate limiting stops
The mechanism is small. You pick a key — an IP, a user ID, or an API key. You pick a window, say ten requests per minute. Past the cap, the server answers 429 Too Many Requests instead of doing the work. That is all it is.
What it stops is much bigger than the mechanism.
- Credential stuffing. Bots take leaked email and password pairs from old breaches and replay them against your login route. With no limit, they can try tens of thousands of pairs in an hour. With a limit, they get ten tries and a wall.
- Scraping. A script can walk your API and pull your whole product list, price list, or user directory in one afternoon. A limit turns that afternoon into weeks, which is usually enough to make the scraper leave.
- Cost blowups. This is the one founders feel first. Any route that calls a paid AI model, sends email or SMS, or renders a PDF costs you money per call. An unthrottled loop against that route runs up your bill while you sleep. The cap on requests is a cap on spend.
One honest limit: rate limiting stops abuse by volume. It does not fix a broken auth check, an exposed key in your bundle, or an open database rule. Those need their own fixes.
Where to add it first
Not every route needs the same cap. Three groups are non-negotiable.
- Auth routes. Login, signup, and token refresh. This is where credential stuffing lives. Keep the limit tight: ten tries per window is plenty for a real person.
- Password reset. Attackers use reset endpoints to find out which emails have accounts, even when login is locked down. Same tight limit.
- Expensive routes. Anything that calls a paid API. AI endpoints top this list. Key the limit per user, not just per IP, so one stolen token cannot drain your budget.
Logged-in traffic needs limits too. A stolen session token can be replayed just like an anonymous request. So apply limits per user and per token, not only at the front door.
The setup, stack by stack
You do not need a custom service for this. Every common stack has a library or a platform switch that does the job in an afternoon.
| Stack | Tool | Shape of the fix |
|---|---|---|
| Node / Express | express-rate-limit | Middleware per route. Tight window on auth, looser on the rest. |
| Next.js / Vercel | @upstash/ratelimit | Edge middleware keyed by IP or user ID. Runs before your code. |
| Python / FastAPI | slowapi | A decorator on each route. Keyed by IP or user. |
| Supabase | Edge Function + counter | Count hits per user per window in a table. Reject past the cap. |
| Cloudflare | Rate limiting rules | Throttle at the edge, before requests ever reach your server. |
Node / Express
npm install express-rate-limit
const rateLimit = require("express-rate-limit");
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 10, // 10 tries per window
standardHeaders: true,
});
app.use("/api/login", authLimiter);
app.use("/api/reset-password", authLimiter);
Next.js on Vercel
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "1 m"),
});
export async function middleware(req) {
const ip = req.headers.get("x-forwarded-for") ?? "unknown";
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response("Too many requests", { status: 429 });
}
}
Python / FastAPI
pip install slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/login")
@limiter.limit("10/minute")
def login(request: Request):
...
On Supabase, put the check inside an Edge Function: read a counter row for the user and the current window, reject with a 429 past the cap, and let a scheduled job clear old rows. And whatever the stack, a Cloudflare rule in front costs nothing and throttles floods before they touch your server.
The pattern never changes. Pick a key. Pick a window. Return 429 past the cap. Simple — but only if someone wires it in.
How to test it with a burst
Do not trust the middleware because you added it. Test it the way an attacker would.
- Pick one guarded endpoint. Start with login.
- Fire 50 requests in about 10 seconds. k6 makes this a one-file job.
- Watch the responses. You should see
429long before request 50. - Check that a different user or IP still gets through. Limits should block the flood, not the customer.
- Repeat for password reset and every paid API route.
// burst.js — run with: k6 run burst.js
import http from "k6/http";
export const options = {
vus: 20, // 20 virtual users
duration: "10s", // one short burst
};
export default function () {
http.post("https://yourapp.com/api/login", {
email: "test@example.com",
password: "wrong",
});
}
This is exactly how our scanner checks your app. We run a short concurrent load against your live URL, and when nothing throttles, the k6 engine reports it word for word:
"No rate limiting detected under concurrent load." — k6Scanner.js:170. One of the most common findings across the 100 AI-built apps we scanned.
No time to script it? Point our free API rate limit checker at one endpoint. It runs the burst for you and tells you if the route ever throttled.
Why AI-built apps ship without it
Tools like Lovable, Bolt, Cursor, and v0 aim at one thing: a working feature, fast. Rate limiting is not a feature a user sees. So nobody prompts for it, and the AI does not add it on its own.
The numbers back this up. Symbiotic Security scanned 1,072 AI-built apps and found 98% had at least one flaw, and 16% had critical issues. CMU's SusVibes study found 61% of AI-written code carried at least one security flaw. Our own average across 100 scanned apps sits at 42/100. The tools are good. The defaults are not — the same story our vibe coding security guide tells for every platform.
And missing limits rarely travel alone. The same builds tend to skip Row Level Security — the exact default behind CVE-2025-48757, which exposed 170+ AI-built apps — and ship without security headers. If your app failed the burst test, check those next. Our free RLS checker covers the database side.
One compliance note
An endpoint that lets anyone scrape user data at full speed is hard to defend as an "appropriate technical measure" under GDPR. And if your product has an AI feature and EU users, the EU AI Act's transparency duties (Article 50) apply from August 2, 2026, with fines up to €15 million or 3% of turnover for that tier. Unthrottled AI endpoints make both problems worse. Our Compliance Wing checks the full list.
What a check costs
The free scan is $0. It tests your live URL — rate limits under load, exposed keys, database rules, headers, monitoring — and returns a Launch Readiness Score out of 100 in about a minute. The full Launch Readiness Audit is $499 one time: a branded report, every finding ranked with time estimates, a benchmark against 200+ audited apps, and a senior review of each item. Ongoing monitoring starts at $149/mo. Start with the scan. It tells you if you need anything more.
FAQ
What is API rate limiting?
API rate limiting caps how many requests one user, IP, or key can send in a set window. Anything past the cap gets a 429 response. It is the simplest control that stops abuse by volume.
What does rate limiting stop?
Three things founders feel first: credential stuffing on login, scrapers pulling your data, and runaway bills on routes that call paid APIs. It does not fix broken auth or open database rules. Those need their own checks.
Where should I add rate limits first?
Login, signup, and password reset come first. Then any route that calls a paid API, sends email or SMS, or writes to your database. Public read routes can wait.
Do Lovable, Bolt, or Cursor add rate limiting for me?
No. AI builders ship the feature a user sees, and throttling is not one of those. Across 100 AI-built apps we scanned, the average readiness score was 42/100, and missing rate limits were one of the most common reasons.
How do I test my rate limits?
Fire a short burst at one endpoint. Use k6 or a simple loop to send 50 requests in 10 seconds. You should see 429 responses long before the burst ends. If every request lands, you have no limit.
Can a free scan catch missing rate limiting?
Yes. Our free scan tests your live URL under a short concurrent load and flags routes that never throttle. It takes about 60 seconds and needs no code access.
Does your login route throttle? Find out now.
Test one endpoint with the free rate limit checker, or scan your whole app. No code access. About 60 seconds.
Check an endpoint freeRate limiting is one line item, but it is a good proxy for the rest of your build. If it is missing, the headers, the database rules, and the monitoring probably got the same treatment. Run the free scan and get the whole list at once.