The SaaS Founder's Guide to API Rate Limiting | Deployxa

Rate limiting protects your SaaS from abuse, DDoS, and resource exhaustion. Here is the founder's guide to implementing it correctly.

← Back to Dispatch Articles
Engineering Log

The SaaS Founder's Guide to API Rate Limiting

Rate limiting protects your SaaS from abuse, DDoS, and resource exhaustion. Here is the founder's guide to implementing it correctly.

The SaaS Founder's Guide to API Rate Limiting

Key Facts

  • Direct answer: The direct answer is that rate limiting protects your SaaS by limiting requests per user (or IP) per time window. The key endpoints to protect are login (brute-force prevention), signup (fake account prevention), password reset (email bombing prevention), and public API endpoints (scraping prevention).

  • Why Rate Limiting Matters for SaaS: Rate limiting matters for three business reasons.

  • Rate Limit Responses: When a user exceeds the rate limit, return a 429 (Too Many Requests) status code with these headers.

If your SaaS has a public API (or even just public endpoints like login and signup), it is vulnerable to abuse: brute-force attacks, scraping, DDoS, and resource exhaustion. Rate limiting is the mechanism that prevents abuse by limiting the number of requests a user (or IP) can make in a given time period. Without rate limiting, a single attacker can overwhelm your app and cause an outage for all customers. This article is the founder's guide to implementing rate limiting correctly.

The direct answer is that rate limiting protects your SaaS by limiting requests per user (or IP) per time window. The key endpoints to protect are login (brute-force prevention), signup (fake account prevention), password reset (email bombing prevention), and public API endpoints (scraping prevention). For more on security, see our article on a practical security checklist for early-stage SaaS.

Why Rate Limiting Matters for SaaS

Rate limiting matters for three business reasons:

  • Prevents outages. Without rate limiting, an attacker (or even an enthusiastic user) can send thousands of requests per second, overwhelming your app and causing an outage for all customers. Rate limiting ensures no single user can consume all your resources.
  • Protects revenue. If your SaaS processes payments, a brute-force attack on the login endpoint can compromise customer accounts, which leads to fraudulent charges and chargebacks. Rate limiting on the login endpoint prevents brute-force attacks.
  • Reduces costs. Without rate limiting, a scraping bot can consume your API quota (e.g., OpenAI API calls, Stripe API calls), which increases your costs. Rate limiting prevents excessive API usage.

Which Endpoints to Rate Limit

High Priority (Protect Immediately)

  • Login. Limit to 5 attempts per minute per IP. This prevents brute-force password attacks.
  • Signup. Limit to 5 signups per hour per IP. This prevents mass account creation.
  • Password reset. Limit to 3 requests per hour per IP and per email. This prevents email bombing (sending thousands of password reset emails).
  • Public API endpoints. Limit to 100 requests per minute per API key (or per IP for unauthenticated endpoints). This prevents scraping and resource exhaustion.

Medium Priority (Protect as You Grow)

  • Search. Limit to 30 searches per minute per user. This prevents search-based scraping.
  • Export. Limit to 5 exports per hour per user. This prevents data exfiltration.
  • File upload. Limit to 10 uploads per minute per user. This prevents storage abuse.

Low Priority (Protect for Enterprise)

  • Read endpoints (authenticated). Limit to 1000 requests per minute per user. This prevents API abuse while allowing normal usage.
  • Write endpoints (authenticated). Limit to 100 requests per minute per user. This prevents data modification abuse.

How to Implement Rate Limiting

For Express (Node.js)

const rateLimit = require('express-rate-limit');

// Login rate limiter
const loginLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 5, // 5 attempts per minute
  message: { error: 'Too many login attempts. Try again in a minute.' },
});

app.post('/login', loginLimiter, loginHandler);

// API rate limiter
const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  keyGenerator: (req) => req.user?.id || req.ip,
  message: { error: 'Too many requests. Slow down.' },
});

app.use('/api', apiLimiter);

For FastAPI (Python)

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.post("/login")
@limiter.limit("5/minute")
def login(request: Request):
    # ...

For Distributed Setups (Multiple Containers)

If you have multiple containers, the default in-memory rate limiter does not work (each container has its own count). Use Redis as the shared store:

const RedisStore = require('rate-limit-redis');
const IORedis = require('ioredis');

const redisClient = new IORedis(process.env.REDIS_URL);

const loginLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redisClient.call(...args) }),
  windowMs: 60 * 1000,
  max: 5,
});

For more on Redis setup, see our article on the SaaS founder's guide to background jobs.

Rate Limit Responses

When a user exceeds the rate limit, return a 429 (Too Many Requests) status code with these headers:

  • Retry-After: The number of seconds until the user can make another request.
  • X-RateLimit-Limit: The maximum number of requests per window.
  • X-RateLimit-Remaining: The number of requests remaining in the current window.
  • X-RateLimit-Reset: The time (epoch seconds) when the window resets.
{
  "error": "Too many requests. Please try again in 60 seconds."
}

Common Pitfalls and Troubleshooting

The first pitfall is not rate limiting the login endpoint. Without rate limiting, an attacker can brute-force passwords (thousands of attempts per second). The fix is to limit to 5 attempts per minute per IP.

The second pitfall is rate limiting too aggressively. If the limit is too low (e.g., 10 requests per minute for an API), legitimate users are blocked. The fix is to set reasonable limits (100 per minute for API, 5 per minute for login).

The third pitfall is not using a shared store for multiple containers. If each container has its own rate limit count, the effective limit is multiplied by the number of containers. The fix is to use Redis as the shared store.

The fourth pitfall is not returning the correct status code. Some apps return 500 (Internal Server Error) when rate limited, which confuses clients. The fix is to return 429 (Too Many Requests) with the Retry-After header.

The fifth pitfall is not handling rate limit errors gracefully in the frontend. When the frontend receives a 429, it should show a friendly message ("Too many requests. Please wait a moment and try again.") instead of a generic error. The fix is to handle 429 errors in the frontend's error handler.

Conclusion: Limit Abuse, Protect Customers

Rate limiting is not optional for a SaaS with public endpoints. By limiting the login, signup, password reset, and API endpoints, you prevent brute-force attacks, scraping, DDoS, and resource exhaustion. The key is to set reasonable limits (not too strict, not too loose) and to use a shared store (Redis) for multiple containers.

Ready to add rate limiting? Install express-rate-limit (or equivalent), configure limits for your key endpoints, and test with a load testing tool. For more, see the rate limiting gap and a practical security checklist for early-stage SaaS. Explore our free developer tools to speed up your workflow.

Ready to deploy with Deployxa?

Deploy your apps globally with automatic SSL and AI diagnostics.

Start Free Now