API Key Rate Limiting — Controlling Third-Party Client Access by API Key
In this tutorial, you will learn about API Key Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.
API key rate limiting applies rate limits based on the API key presented in the request, allowing different clients to have different limits based on their subscription tier or trust level.
What You'll Learn
- How API key rate limiting differs from IP-based limiting
- How to extract and validate API keys from requests
- How to enforce per-key limits with different tiers
Why It Matters
IP-based limiting is unreliable with shared IPs (NAT, VPNs). API key limiting maps traffic to specific clients regardless of their IP address, enabling accurate per-customer tracking, billing based on usage, and tiered access control.
Real-World Use
DodaTech's threat intelligence API provides API keys to partners. Each key has a limit based on the partner's plan: Free tier = 100 req/hr, Pro = 1000 req/hr, Enterprise = 10000 req/hr. API key limiting ensures each partner stays within their plan while preventing one partner's traffic from affecting others.
flowchart LR
A["Request\n+ API Key"] --> B{"Extract &\nvalidate key"}
B -->|"Key: free_abc"| C["Free: 100 req/hr"]
B -->|"Key: pro_xyz"| D["Pro: 1000 req/hr"]
B -->|"Key: ent_123"| E["Enterprise: 10000 req/hr"]
C --> F{"Under limit?"}
D --> F
E --> F
F -->|Yes| G["Process Request"]
F -->|No| H["429 Rate Limited"]
style B fill:#dbeafe,stroke:#2563eb
style H fill:#fecaca,stroke:#dc2626
Implementation
import redis
from flask import Flask, request, jsonify, g
from functools import wraps
app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0)
# API key configuration
API_KEYS = {
"free_a1b2c3": {"tier": "free", "limit": 100, "window": 3600},
"pro_d4e5f6": {"tier": "pro", "limit": 1000, "window": 3600},
"ent_g7h8i9": {"tier": "enterprise", "limit": 10000, "window": 3600},
}
def get_api_key():
api_key = request.headers.get('X-API-Key')
if not api_key:
api_key = request.args.get('api_key')
return api_key
def require_api_key(f):
@wraps(f)
def decorated(*args, **kwargs):
api_key = get_api_key()
if not api_key:
return jsonify({"error": "missing_api_key"}), 401
if api_key not in API_KEYS:
return jsonify({"error": "invalid_api_key"}), 403
g.api_key = api_key
g.api_key_config = API_KEYS[api_key]
return f(*args, **kwargs)
return decorated
def rate_limit_by_api_key():
api_key = g.api_key
config = g.api_key_config
key = f"rl:apikey:{api_key}"
window = config["window"]
limit = config["limit"]
current = r.get(key)
if current and int(current) >= limit:
return jsonify({
"error": "rate_limit_exceeded",
"tier": config["tier"],
"limit": limit,
"retry_after": r.ttl(key)
}), 429
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, window)
pipe.execute()
@app.route('/api/data')
@require_api_key
def get_data():
limit_response = rate_limit_by_api_key()
if limit_response:
return limit_response
return jsonify({"data": "response data"})
Tiered Key Management
import secrets
import hashlib
def generate_api_key(prefix="doda"):
key = secrets.token_urlsafe(32)
return f"{prefix}_{key}"
def hash_api_key(key):
return hashlib.sha256(key.encode()).hexdigest()
# Batch create keys
def create_tier_keys(tier, count):
keys = []
for i in range(count):
raw_key = generate_api_key(tier[:3])
hashed = hash_api_key(raw_key)
keys.append({"raw": raw_key, "hashed": hashed, "tier": tier})
print(f"Created {tier} key: {raw_key}")
return keys
Common Mistakes
1. Storing API Keys in Plain Text
Store hashed API keys in your database. Hash the incoming key before lookup to prevent credential theft from database breaches.
2. Using API Keys Without TLS
API keys are bearer tokens. Anyone intercepting the key can use it. Always enforce HTTPS.
3. Not Allowing Key Rotation
Partners need to rotate compromised keys. Provide an API endpoint or UI for key revocation and regeneration.
4. Rate Limiting by Key Hash Before Validation
Check key validity before applying rate limits. Invalid keys should not count against any limit.
5. Exposing the Key In URLs
API keys in query parameters appear in server logs, browser history, and referrer headers. Use the Authorization header instead.
Practice Questions
- Why is API key limiting better than IP limiting for third-party APIs?
- How should API keys be stored in the database?
- What header should carry the API key?
- How do you handle key rotation?
- What is the risk of exposing keys in URLs?
Answers
- API keys identify clients regardless of IP, enabling accurate per-client tracking. 2. Store hashed keys (SHA-256), not plaintext. 3. The
Authorizationheader orX-API-Keycustom header. 4. Provide a revocation/regeneration endpoint. 5. Keys appear in server logs and browser history.
Challenge
Build an API key management system with key generation, hashed storage, rotation support, per-key rate limit configuration, and a middleware that validates, rate-limits, and logs each API request with the key identifier.
FAQ
Mini Project
Create a complete API key management service with: key generation for different tiers, hashed storage, an admin API for CRUD operations on keys, rate limiting middleware, usage analytics per key, and a dashboard showing current usage vs limits.
What's Next
- Learn about quota-based limits for daily and monthly usage caps
- Explore tiered rate limiting for free/pro/enterprise plans
- Continue to rate limit headers and how clients should interpret them
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro