Rate Limiting Quotas — Daily and Monthly Usage Caps for API Consumers
In this tutorial, you will learn about Rate Limiting Quotas. We cover key concepts, practical examples, and best practices to help you master this topic.
Quota-based rate limiting enforces long-term usage limits (daily, weekly, monthly) in addition to short-term rate limits, providing predictable resource consumption and enabling usage-based billing for API products.
What You'll Learn
- How quotas differ from rate limits
- How to implement daily and monthly quota tracking
- How to communicate quota status to API consumers
Why It Matters
Short-term rate limits (e.g., 100 req/min) prevent traffic spikes. Quotas (e.g., 10,000 req/month) prevent runaway consumption. Together they provide complete usage control. Without quotas, a client could stay under the per-minute limit but consume months of resources in a single day.
Real-World Use
DodaTech's API has both rate limits and quotas: 100 req/min rate limit and 50,000 req/month quota per partner. A partner sends 80 req/min consistently. The rate limit is not triggered, but after 625 minutes (~10 hours) they hit the monthly quota. The API rejects further requests until the next billing cycle.
flowchart LR
A["API Request"] --> B{"Rate limit\ncheck"}
B -->|"Pass"| C{"Daily quota\ncheck"}
C -->|"Pass"| D{"Monthly quota\ncheck"}
D -->|"Pass"| E["Process\nRequest"]
B -->|"Fail"| F["429 Rate\nLimit"]
C -->|"Exceeded"| G["429 Daily\nQuota"]
D -->|"Exceeded"| H["429 Monthly\nQuota"]
style F fill:#fecaca,stroke:#dc2626
style G fill:#fef3c7,stroke:#d97706
style H fill:#fca5a5,stroke:#dc2626
Implementation
import redis
import time
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, g
app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, db=0)
QUOTA_CONFIG = {
"free": {
"rate_limit": 10,
"rate_window": 60,
"daily_quota": 1000,
"monthly_quota": 10000
},
"pro": {
"rate_limit": 100,
"rate_window": 60,
"daily_quota": 50000,
"monthly_quota": 500000
},
"enterprise": {
"rate_limit": 1000,
"rate_window": 60,
"daily_quota": 500000,
"monthly_quota": 5000000
}
}
def check_quota(api_key, tier):
config = QUOTA_CONFIG[tier]
now = datetime.utcnow()
# Daily quota key
daily_key = f"quota:daily:{api_key}:{now.strftime('%Y%m%d')}"
# Monthly quota key
monthly_key = f"quota:monthly:{api_key}:{now.strftime('%Y%m')}"
daily_used = int(r.get(daily_key) or 0)
monthly_used = int(r.get(monthly_key) or 0)
if daily_used >= config["daily_quota"]:
return "daily_quota_exceeded", config["daily_quota"], daily_used
if monthly_used >= config["monthly_quota"]:
return "monthly_quota_exceeded", config["monthly_quota"], monthly_used
pipe = r.pipeline()
pipe.incr(daily_key)
pipe.expire(daily_key, 86400)
pipe.incr(monthly_key)
pipe.expire(monthly_key, 86400 * 30)
pipe.execute()
return None, config["monthly_quota"], monthly_used
@app.after_request
def add_quota_headers(response):
if hasattr(g, 'api_key') and hasattr(g, 'tier'):
config = QUOTA_CONFIG[g.tier]
daily_key = f"quota:daily:{g.api_key}:{datetime.utcnow().strftime('%Y%m%d')}"
monthly_key = f"quota:monthly:{g.api_key}:{datetime.utcnow().strftime('%Y%m')}"
response.headers['X-RateLimit-Daily-Remaining'] = config["daily_quota"] - int(r.get(daily_key) or 0)
response.headers['X-RateLimit-Monthly-Remaining'] = config["monthly_quota"] - int(r.get(monthly_key) or 0)
response.headers['X-RateLimit-Daily-Limit'] = config["daily_quota"]
response.headers['X-RateLimit-Monthly-Limit'] = config["monthly_quota"]
return response
Quota Reset Logic
def get_quota_reset_times():
now = datetime.utcnow()
# Daily reset: midnight UTC
daily_reset = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
daily_seconds = int((daily_reset - now).total_seconds())
# Monthly reset: 1st of next month
if now.month == 12:
monthly_reset = now.replace(year=now.year + 1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
else:
monthly_reset = now.replace(month=now.month + 1, day=1, hour=0, minute=0, second=0, microsecond=0)
monthly_seconds = int((monthly_reset - now).total_seconds())
return {
"daily_reset_seconds": daily_seconds,
"monthly_reset_seconds": monthly_seconds
}
Common Mistakes
1. Confusing Rate Limits with Quotas
Rate limits prevent short-term bursts; quotas control long-term usage. Both are needed but serve different purposes.
2. Not Persisting Quota Counters
Quota counters must survive server restarts. Use Redis with persistence or a database instead of in-memory counters.
3. Ignoring Timezone Boundaries
If your servers are in different timezones, quota resets at different times. Use UTC for all quota tracking.
4. Not Handling Quota Exceeded Gracefully
Return 429 with a clear message showing which quota was exceeded and when it resets. Help the client understand the limit.
5. Allowing Quota Overages Without Notification
If you allow overages (billing overage), notify the client at 80%, 90%, and 100% of quota consumption.
Practice Questions
- What is the difference between a rate limit and a quota?
- How do you track monthly quotas in Redis?
- What headers should communicate quota status?
- When do daily quotas typically reset?
- How should you handle quota exceeded errors?
Answers
- Rate limits control per-second/minute bursts; quotas control daily/monthly total usage. 2. Use a Redis key with a date-based pattern (daily:YYYYMMDD, monthly:YYYYMM). 3. X-RateLimit-Daily-Remaining, X-RateLimit-Monthly-Remaining, and reset times. 4. At midnight UTC. 5. Return 429 with a JSON body explaining which quota was exceeded and when it resets.
Challenge
Build a quota tracking service that supports hourly, daily, and monthly quotas with configurable reset schedules, handles timezone-aware resets, sends webhook notifications at configurable thresholds (80%, 90%, 100%), and provides a real-time usage dashboard.
FAQ
Mini Project
Create a quota management system with a PostgreSQL-backed quota tracker, a FastAPI middleware, a management UI for viewing and adjusting quotas, email notifications at threshold levels, and a Grafana dashboard showing quota consumption trends.
What's Next
- Learn about tiered rate limiting for free/pro/enterprise plans
- Explore rate limit headers for client-side consumption
- Continue to retry-after header and client backoff strategies
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro