The SaaS Founder's Guide to API Rate Limiting
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.
Common Pitfalls and Troubleshooting
When working with the saas founder's guide to api rate limiting, several common pitfalls can undermine effectiveness. The first is over-automation. Automating everything sounds appealing, but some tasks require human judgment. The fix is to automate repetitive tasks (monitoring, diagnosis, deployment) while keeping humans in the loop for decisions that affect customers, billing, or security. The second is not testing changes before applying them. Whether it is a configuration change, a code change, or an infrastructure change, untested changes can break production. The fix is to always test in staging before applying to production, and to have a rollback plan. The third is not monitoring the automation itself. If your automated system goes down, you are flying blind. The fix is to monitor the automation system (e.g., with a dead man's switch) and to alert if it stops running. The fourth is not documenting the process. If the process is in your head, it does not exist for anyone else. The fix is to document the process in a runbook that anyone can follow. For more on documentation, see our article on how to build a deployment process your future team can inherit. The fifth is not reviewing regularly. Processes that work today might not work tomorrow (as the product grows, the traffic changes, the team changes). The fix is to review the process monthly and to adjust as needed.
Advanced Patterns and Best Practices
Beyond the basics of the saas founder's guide to api rate limiting, several advanced patterns can improve outcomes. The first is incremental implementation. Rather than implementing everything at once, start with the minimum viable version and iterate. This reduces risk (smaller changes are easier to debug) and delivers value faster. The second is automation. Manual processes are error-prone and do not scale. The fix is to automate repetitive tasks (deployment, testing, monitoring) using CI/CD pipelines and automated tools. For more on CI/CD, see our article on how we built the CI/CD pipeline. The third is documentation. A process that is not documented does not exist for anyone else. The fix is to document processes in runbooks that anyone can follow. For more on documentation, see our article on how to build a deployment process your future team can inherit. The fourth is testing. Untested changes can break production. The fix is to write tests (unit, integration, end-to-end) and to run them in CI/CD before every deployment. For more on testing, see our article on the testing void. The fifth is continuous improvement. Processes that work today might not work tomorrow. The fix is to review processes regularly (monthly) and to adjust based on lessons learned from incidents, feedback, and changing requirements.
When This Approach Is Not the Right Choice
While the saas founder's guide to api rate limiting is a valuable practice, it is not always the right approach. For very small projects (hobby projects, prototypes), the overhead of implementing best practices might not be worth the effort. The fix is to implement the minimum viable version and to add more as the project grows. For teams with limited resources (solo founders, small teams), prioritizing features over infrastructure might be the right call in the short term. The fix is to implement the highest-impact practices first (security, backups) and to defer the rest until the team grows. For projects with strict compliance requirements (HIPAA, SOC 2), the standard approach might not be sufficient, and you might need to implement additional controls (audit logging, access reviews, penetration testing). The key is to match the approach to your project's stage, resources, and requirements. For more on prioritization, see our article on the production checklist before your SaaS takes its first customer. For more on compliance, see the SaaS founder's guide to compliance.
Additional Considerations and Best Practices
When working with the saas founder's guide to api rate limiting, there are several additional considerations that can significantly impact your success. The first is the importance of starting simple and iterating. Many teams try to implement everything at once, which leads to complexity, bugs, and delayed launches. The fix is to start with the minimum viable version, verify it works, and then add features incrementally. This approach reduces risk, delivers value faster, and makes debugging easier because changes are smaller. The second consideration is the importance of documentation. A process that is not documented does not exist for anyone else on the team. Document your configuration, your deployment process, your rollback procedure, and your incident response plan. Use runbooks that anyone can follow, not just the person who set up the system. For more on documentation, see our article on how to build a deployment process your future team can inherit.
The third consideration is testing. Untested changes are the leading cause of production incidents. Before deploying any change, test it locally, test it in staging, and run your automated test suite. If you do not have automated tests, start by writing tests for your most critical paths (signup, login, payment). For more on testing, see our article on the testing void. The fourth consideration is monitoring. Without monitoring, you cannot detect issues until customers complain. Set up health checks, structured logging, metrics tracking, and alerts for error rate and response time. For more on monitoring, see our article on monitoring your SaaS without hiring a DevOps engineer.
The fifth consideration is security. Security is not optional when you are handling customer data and payment information. Ensure all secrets are in environment variables (never hardcoded), enforce HTTPS, set security headers, use rate limiting on auth endpoints, and hash passwords with bcrypt or argon2. For more on security, see our article on a practical security checklist for early-stage SaaS. The sixth consideration is backups and recovery. Your database should be backed up daily, backups should be stored off-site, and backup restore should be tested regularly. An untested backup is not a backup. For more on backups, see our article on how to rehearse a database restore before you need one.
The seventh consideration is cost management. Cloud costs can creep up over time, and without monitoring, they can exceed revenue. Track your monthly hosting cost, set a budget, and use fixed pricing (like Deployxa at $9/month for 15 apps) to avoid surprise bills. For more on cost management, see our article on how to estimate deployment costs for a small SaaS. The eighth consideration is team communication. When things go wrong, communication is as important as the fix. Set up a status page, communicate transparently during incidents, and publish post-mortems after. For more on communication, see our article on the SaaS founder's guide to status pages.
These considerations apply regardless of your specific technology stack, team size, or business model. By addressing each one systematically, you reduce the risk of outages, data loss, security breaches, and cost overruns, which protects your revenue and your customers' trust.
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.