Your API scales. That's the pitch. What you didn't notice is that it scales the attacker's costs too: one anonymous user can now mint you a five-figure AWS bill with a bash loop. The endpoint works exactly as designed. The design is what's missing.
Every API call consumes something: CPU, memory, database time, bandwidth, an LLM token bill, a carrier SMS fee. Unrestricted Resource Consumption is what happens when the API has no idea how much any request costs and no mechanism to stop a caller from making too many.
What your AI actually built
You asked for a /api/search endpoint that takes a query and a limit. The model delivered. No upper bound on the limit, no pagination ceiling, no cap on concurrent calls, no rate limit per caller.
Somewhere else in the app there is a /api/send-otp route that calls Twilio for every request, an /api/chat route that forwards to OpenAI with no token cap, and an /api/export route that materializes the entire database into a CSV on the fly.
Each of these is one feature the model wrote correctly. What it did not add — because you did not ask — is any notion of how much any of them costs to invoke and who is allowed to invoke them how many times.
How it gets exploited
An attacker finds the API surface of a SaaS product from its public docs. No login required for a few endpoints, login required for the rest.
- 1Find the expensive endpointThey look for the route that does the most work per call. /api/export/csv wins — it streams the whole projects table through a transformer.
- 2Turn up the volumeThey call it in a loop from 50 IPs. The backend fans out to the database, which fans out to the read replica, which starts queueing. The service degrades for every real customer simultaneously.
- 3Point at the expensive dependencyOn the unauthenticated /api/chat endpoint, every request costs 30 cents in LLM tokens. They run 10,000 requests an hour. The Anthropic bill for the day is $72,000.
- 4Abuse the SMS endpointMeanwhile /api/send-otp takes a phone number and sends a code. They pipe an entire country code range through it. Twilio charges per send. Nothing rate limits by IP, by user, or by recipient.
The app is still up, technically. Three different vendors are sending the founder urgent billing emails. Real customers can't load the dashboard. The only mitigation the team has is to pull the plug.
Vulnerable vs Fixed
// app/api/search/route.ts
export async function POST(req) {
const { query, limit } = await req.json();
// limit: 1000000 is a perfectly legal request
const results = await db.product.findMany({
where: { name: { contains: query } },
take: limit,
});
return Response.json(results);
}// app/api/search/route.ts
import { z } from 'zod';
import { rateLimit } from '~/lib/rate-limit';
const SearchSchema = z.object({
query: z.string().min(1).max(200),
limit: z.number().int().min(1).max(50).default(20),
});
export async function POST(req) {
const session = await getSession(req);
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });
const ok = await rateLimit(`search:${session.userId}`, { max: 60, window: '1m' });
if (!ok) return Response.json({ error: 'Too many requests' }, { status: 429 });
const { query, limit } = SearchSchema.parse(await req.json());
const results = await db.product.findMany({
where: { name: { contains: query } },
take: limit,
});
return Response.json(results);
}Three layers: a schema that caps the input (limit max 50, query max 200 chars), an auth check so anonymous floods bounce, and a per-user rate limit that stops even authenticated abuse. Apply the same three layers to anything that calls a paid API.
A real case
A single uncapped SMS endpoint cost one startup $28,000 in a weekend
A common SMS pumping pattern — attackers send OTP requests to premium-rate numbers they control and split the carrier revenue — has drained countless startup budgets through uncapped /send-verification endpoints.
Related reading
References
Find the endpoints that scale the wrong way.
Flowpatrol maps your API, measures cost per call, and flags the routes that will ruin your month.
Try it free