Skip to content

Rate Limiting Quotas — Daily and Monthly Usage Caps for API Consumers

DodaTech Updated 2026-06-28 4 min read

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

  1. What is the difference between a rate limit and a quota?
  2. How do you track monthly quotas in Redis?
  3. What headers should communicate quota status?
  4. When do daily quotas typically reset?
  5. How should you handle quota exceeded errors?

Answers

  1. 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

What is a quota in rate limiting?

A long-term usage limit (daily, monthly) that caps total consumption over a period.

How are quotas different from rate limits?

Rate limits control bursts per second/minute; quotas control total consumption per day/month.

When do daily quotas reset?

Typically at midnight UTC, but can be configured per customer.

What HTTP status code indicates quota exceeded?

429 Too Many Requests, with a body explaining the quota type.

Can quotas be increased mid-cycle?

Yes. Implement a mechanism to adjust quotas manually or via plan upgrade.

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