Rate limiting for APIs and forms: how we set the limits

How we decide the numbers behind rate limits on forms, logins and APIs, what happens when they are hit, and how we tune them from real traffic.

4 minread 808words last updated

The short answer

Rate limits are only useful if the numbers are right: too tight and real users are blocked, too loose and abuse walks through. We set them per endpoint, because a login, a form and a search have different genuine patterns; per source, so one abuser does not affect anyone else; and per account or per key where the endpoint has one, so attacks on a single user or a single integration are contained. The starting numbers come from real usage in the logs, with a wide margin above the busiest genuine pattern, and are tightened only with evidence. Logins are slowed rather than locked, so attackers are throttled without being handed a way to lock users out. APIs get per-key quotas matched to the plan or the integration, plus global ceilings that bound cost. Every limit returns a clear response with a retry time and is logged, so genuine users understand what happened and abuse patterns are visible.

Limits by endpoint type

EndpointDimensionsStarting shapeBehaviour when hit
Contact and quote formsPer source; global dailyA few per minute per source, several times the genuine peak; a daily ceilingClear message, retry time, input preserved
LoginPer account; per sourceA handful of failures, then increasing delay; source limited after moreSlowing, then limit; owner notified of repeated failures
Password resetPer account; per sourceLow, because genuine use is rareMessage says an email was sent if the account exists
SearchPer sourceTens per minuteMessage with retry
Public APIPer key; per source; globalQuota matching the plan; burst allowance; global ceilingStatus code, remaining allowance and reset headers
Webhook receiversPer sender; globalGenerous, because senders retryAccept and queue; reject only floods
Expensive functionsPer source; globalTight per source; global ceiling for costMessage; alert to the team when the ceiling is approached
Newsletter sign-upPer source; per addressLowMessage; confirmation flow unchanged

How we tune them

  1. Measure genuine usage per endpoint from a few weeks of logs.
  2. Set limits several times above the genuine peak, per source, per account, per key.
  3. Choose the behaviour: slowing for logins, clear rejection for forms, headers for APIs.
  4. Add signals rather than tightening blindly: bot checks on forms, honeypots, reputation from the network layer.
  5. Log every limit event with endpoint, dimension and source.
  6. Review weekly at first, then monthly: raise where genuine users were hit, add signals where abuse got through.
  7. Set global ceilings on cost-bearing endpoints so the bill has a bound no matter what.

Limits and the rest of the defence

Rate limits work with, not instead of, the other layers: the network layer in front filtering known bad traffic, bot checks on forms, authentication on APIs, input validation everywhere, and monitoring that notices patterns. A limit is the backstop that bounds what any single source can do when the other layers let something through, and its log is where new abuse patterns first appear.

What this means for you

Good rate limits are per endpoint, per source and per account or key, set from real usage with a wide margin, tuned with evidence, slowing rather than locking on logins, bounded globally on anything that costs money, and always visible when they fire. Set that way, they stop abuse without ever being noticed by a genuine user, and their log becomes one of your most useful security signals.

Written by the CivSec S.M.A.R.T team

We build and run websites, software and AI systems for businesses. We write about what we see in that work, in plain language, and we update articles when things change.

Last checked . Spotted something outdated? Tell us.

Frequently asked questions

How do you decide the actual numbers?

From traffic, once there is traffic to read. The method: take the busiest genuine pattern for an endpoint over a few weeks, such as the most form submissions a real visitor made in a minute, and set the limit several times higher. Until those measurements exist, a limit is a generous default rather than a derived number, and that is what ours is today: it sits high enough that a real visitor will not meet it. The number becomes evidence the moment someone reads the traffic and adjusts it.

Why not lock accounts after failed logins?

Because an attacker can then lock any user out by failing on purpose, which is a denial of service you built yourself. Instead we slow down: after a few failures, each attempt waits longer, and after more, the source is limited and the account owner is notified. Attackers are throttled to uselessness; genuine users who mistype twice barely notice.

What does a user see when they hit a limit?

A plain message saying too many requests were made and when to try again, with what they typed preserved, and a proper status code so any automated client understands. For APIs, headers tell the client its remaining allowance and when it resets, so well-behaved integrations back off on their own.

Sources

  1. OWASP API Security Top 10: Unrestricted Resource Consumption (accessed 2026-09-14)