Endpoint-Based Rate Limiting — Per-Route API Traffic Control
In this tutorial, you will learn about Endpoint. We cover key concepts, practical examples, and best practices to help you master this topic.
Endpoint-based Rate Limiting applies distinct rate limits to different API routes, allowing critical or expensive endpoints to have stricter limits while read-only or cheap endpoints can handle more traffic.
What You'll Learn
- What endpoint-based rate limiting is and when to use it
- How to configure different limits for different API routes
- How to implement endpoint-based limiting with Redis
Why It Matters
Not all endpoints have the same cost or risk profile. An authentication endpoint should have strict limits (5 requests per minute) to prevent brute-force attacks, while a public data endpoint might allow 1000 requests per minute. Endpoint-based limiting gives you this granular control.
Real-World Use
DodaTech's API applies endpoint-specific rate limits: /auth/login allows 10 req/min to prevent credential stuffing, /api/search allows 100 req/min per user, and /api/public/status allows 1000 req/min. This protects sensitive operations while keeping public data accessible.
flowchart LR
A["Request"] --> B{"Which endpoint?"}
B -->|"/auth/login"| C["Rate: 10 req/min"]
B -->|"/api/search"| D["Rate: 100 req/min"]
B -->|"/api/public/*"| E["Rate: 1000 req/min"]
C --> F{"Under limit?"}
D --> F
E --> F
F -->|Yes| G["Process Request"]
F -->|No| H["429 Too Many Requests"]
style B fill:#dbeafe,stroke:#2563eb
style H fill:#fecaca,stroke:#dc2626
Configuration Structure
Define endpoint limits in a configuration file:
ENDPOINT_LIMITS = {
"auth": {
"paths": ["/api/auth/login", "/api/auth/register"],
"limit": 10,
"window": 60, # 1 minute
"key_by": "ip"
},
"search": {
"paths": ["/api/search", "/api/suggest"],
"limit": 100,
"window": 60,
"key_by": "user_id"
},
"public": {
"paths": ["/api/public/*"],
"limit": 1000,
"window": 60,
"key_by": "ip"
},
"admin": {
"paths": ["/api/admin/*"],
"limit": 500,
"window": 60,
"key_by": "user_id"
}
}
Implementation with Redis
import time
import re
import redis
from flask import Flask, request, jsonify, g
app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0)
def get_endpoint_config(path):
for name, config in ENDPOINT_LIMITS.items():
for pattern in config["paths"]:
regex = "^" + re.escape(pattern).replace(r'\*', '.*') + "$"
if re.match(regex, path):
return config
return None
@app.before_request
def rate_limit_by_endpoint():
config = get_endpoint_config(request.path)
if not config:
return None
if config["key_by"] == "ip":
key = f"rl:endpoint:{request.path}:{request.remote_addr}"
elif config["key_by"] == "user_id":
user_id = g.get('user_id', 'anonymous')
key = f"rl:endpoint:{request.path}:{user_id}"
current = r.get(key)
if current and int(current) >= config["limit"]:
return jsonify({
"error": "rate_limit_exceeded",
"message": f"Endpoint limit of {config['limit']} per {config['window']}s exceeded",
"retry_after": r.ttl(key)
}), 429
pipe = r.pipeline()
pipe.incr(key, 1)
pipe.expire(key, config["window"])
pipe.execute()
@app.route('/api/auth/login', methods=['POST'])
def login():
return jsonify({"message": "Login endpoint"})
Pattern Matching
Support both exact paths and wildcards:
from fnmatch import fnmatch
def match_endpoint(path, patterns):
for pattern in patterns:
if fnmatch(path, pattern):
return True
return False
# Test pattern matching
test_paths = ["/api/public/status", "/api/public/health", "/api/auth/login"]
patterns = ["/api/public/*", "/api/auth/*"]
for path in test_paths:
matched = match_endpoint(path, patterns)
print(f"{path}: {'Matched' if matched else 'No match'}")
Expected output:
/api/public/status: Matched
/api/public/health: Matched
/api/auth/login: Matched
Common Mistakes
1. Using Path Prefix Only Without Exact Patterns
Prefix matching may capture unintended routes. Use explicit patterns and test that each route maps to the correct limit.
2. Not Ordering Patterns Correctly
If a path matches multiple patterns, the first match wins. Order patterns from most specific to most general.
3. Ignoring HTTP Method in Endpoint Identity
A GET /api/data and POST /api/data may need different limits. Include the HTTP method in the rate limit key.
4. Forgetting Wildcard Prefixes
Sub-routes like /api/v2/search may not match /api/search*. Define explicit patterns for all route versions.
5. Applying Limits to Health Check Endpoints
Health check / monitoring endpoints should typically be excluded from rate limiting to avoid false alerts.
Practice Questions
- Why would an auth endpoint need stricter limits than a search endpoint?
- How do you handle path patterns with wildcards?
- What happens if a request matches multiple endpoint patterns?
- Should health check endpoints be rate limited?
- How do you include HTTP method in endpoint identification?
Answers
- Auth endpoints are targets for brute-force attacks. 2. Use fnmatch or regex pattern matching. 3. The first matching pattern in the configuration is applied. 4. Generally no, to avoid false monitoring alerts. 5. Include
request.methodin the rate limit key.
Challenge
Build an endpoint-based rate limiter that reads a YAML configuration file, supports exact paths and glob patterns, applies per-endpoint limits with HTTP method awareness, and returns appropriate headers showing the specific endpoint limit.
FAQ
Mini Project
Create a Flask middleware that loads endpoint rate limits from a YAML file, supports exact and wildcard path patterns, applies limits per-method, and includes a debug endpoint showing the current rate limit configuration for any given path.
What's Next
- Learn about API-key-based rate limiting for third-party access control
- Explore quota-based limits for daily and monthly usage tracking
- Continue to tiered rate limiting for free/pro/enterprise plans
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro