Input and resource controls
Limit resource consumption
Bound request size, execution time, pagination, uploads, concurrency, and expensive downstream work before one caller can exhaust shared capacity.
An API can be valid and still be too expensive. Attackers look for operations where a small request creates large work — and so do badly written client retry loops, which cause most real capacity incidents.
Bound the obvious dimensions
Set limits at the edge and inside the application. Cap page size, query complexity, file size, processing time, retries, and concurrent jobs.
Pagination is where most APIs leak capacity first:
curl -i "https://api.example.com/invoices?per_page=100000"
# HTTP/1.1 400 Bad Request
# {"error":"per_page must be between 1 and 100"}
Clamping silently to 100 also works. Rejecting makes the limit visible to client developers. Either way, the database never sees an unbounded query.
Rate limiting handles frequency. Return a controlled response and observe repeated limit hits:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Remaining: 0
A caller who hits the limit constantly is either broken or probing. Both deserve a look.
Price operations by their real cost
A 2 KB report request can trigger a minute-long database scan and a 200 MB export. One global rate limit misses this cost difference and may punish cheap requests.
Measure the expensive unit directly: rows scanned, worker seconds, memory, or provider calls. Per-operation budgets make the limit explainable and let ordinary invoice reads keep working:
GET /invoices 600 requests/min per account
POST /reports 5 requests/min, max 2 concurrent per account
POST /invoices/import 1 running job per account
Verify the failure is controlled
Exceeding a limit must produce a clean rejection, not a half-finished job that already burned the resources:
# third concurrent report for the same account
curl -i -X POST https://api.example.com/reports \
-H "Authorization: Bearer $TOKEN"
# HTTP/1.1 429 Too Many Requests
Check the worker queue after the 429: no orphaned job should exist for the rejected request.
Measure database time, response bytes, and queued work for the report endpoint. Exceed one page, timeout, and concurrency limit, then prove the API fails predictably while a normal request still succeeds.
Lesson completed