How to Configure Rate Limiting with Free and Pro Tiers in FastAPI Boilerplate

To configure rate limiting with free and pro tiers in the FastAPI Boilerplate, create tier records in the database, define per-path rate limit rules via the CRUD endpoints, and inject the rate_limiter_dependency into your API routes to enforce Redis-backed sliding-window counters automatically.

The benavlabs/fastapi-boilerplate implements a comprehensive, database-driven rate limiting system that supports multiple subscription tiers without requiring code redeployment. This architecture allows you to define granular request limits for different user tiers—such as free and pro—while using Redis for high-performance, distributed enforcement across multiple API workers.

Understanding the Tier-Aware Rate Limiting Architecture

The system consists of several interconnected components that work together to enforce tier-specific limits dynamically.

Core Components

  • Tier Model (src/app/models/tier.py): Stores subscription levels (e.g., free, pro) in the tier table.
  • Rate Limit Model (src/app/models/rate_limit.py): Defines limit and period values for specific tier-path combinations.
  • Rate Limiter Utility (src/app/core/utils/rate_limit.py): A singleton class implementing the sliding-window counter algorithm with Redis.
  • Dependency Injection (src/app/api/dependencies.py): The rate_limiter_dependency function that resolves users, looks up tiers, and enforces limits.
  • Default Configuration (src/app/core/config.py): Fallback values (DEFAULT_RATE_LIMIT_LIMIT and DEFAULT_RATE_LIMIT_PERIOD) used when no specific rule exists.

Setting Up Free and Pro Tiers

Before enforcing limits, you must define your tiers and their corresponding rate rules in the database.

Creating Tier Records

First, create the tier entries using the admin endpoints. Each tier receives a unique ID used to associate rate limits:


# POST /api/v1/tiers (requires super-user token)

# Create Free tier

{
    "name": "free"
}

# Returns: tier_id = 1

# Create Pro tier  

{
    "name": "pro"
}

# Returns: tier_id = 2

Defining Per-Path Rate Limits

Next, configure specific limits for each tier and endpoint path. The boilerplate allows different limits for the same endpoint based on the user's subscription level:


# Free tier: 10 requests per minute to /api/v1/posts

POST /api/v1/tier/free/rate_limit
{
    "name": "free_posts_minute",
    "path": "/api/v1/posts",
    "limit": 10,
    "period": 60
}

# Free tier: 2 AI generations per hour

POST /api/v1/tier/free/rate_limit
{
    "name": "free_ai_generate", 
    "path": "/api/v1/ai/generate",
    "limit": 2,
    "period": 3600
}

# Pro tier: 60 requests per minute to /api/v1/posts

POST /api/v1/tier/pro/rate_limit
{
    "name": "pro_posts_minute",
    "path": "/api/v1/posts", 
    "limit": 60,
    "period": 60
}

# Pro tier: 50 AI generations per hour

POST /api/v1/tier/pro/rate_limit
{
    "name": "pro_ai_generate",
    "path": "/api/v1/ai/generate",
    "limit": 50,
    "period": 3600
}

Enforcing Rate Limits on API Endpoints

Once tiers and rules are defined, apply rate limiting to your routes using FastAPI's dependency injection system.

Router-Level Protection

Apply the rate_limiter_dependency to an entire router to protect all endpoints within it:

from fastapi import APIRouter, Depends
from app.api.dependencies import rate_limiter_dependency

router = APIRouter(
    prefix="/api/v1/posts",
    dependencies=[Depends(rate_limiter_dependency)]  # Enforces tier-aware limits

)

@router.post("/", response_model=PostRead)
async def create_post(post: PostCreate):
    # Business logic executes only if user hasn't exceeded tier limits

    return await crud_posts.create(db=db, object=post)

Default Limits and Fallback Behavior

When a specific tier-path combination lacks a defined rule in the rate_limit table, the system falls back to the default values configured in src/app/core/config.py. This ensures all protected endpoints have some level of rate limiting even without explicit configuration.

How the Rate Limiting Works Under the Hood

When a request hits an endpoint protected by Depends(rate_limiter_dependency), the following workflow executes:

  1. User Resolution: The dependency obtains the current user or falls back to the client IP address.
  2. Tier Lookup: It queries crud_tiers (defined in src/app/crud/crud_tier.py) to load the user's subscription tier.
  3. Rule Retrieval: It searches crud_rate_limits (from src/app/crud/crud_rate_limit.py) for a rate limit rule matching the tier.id and sanitized request path.
  4. Limit Determination: If a rule exists, it uses the stored limit and period; otherwise, it applies DEFAULT_RATE_LIMIT_LIMIT and DEFAULT_RATE_LIMIT_PERIOD from the configuration.
  5. Redis Counter: The RateLimiter singleton increments a Redis key formatted as ratelimit:{user_id}:{sanitized_path}:{window_start} and checks if the counter exceeds the allowed threshold.
  6. Enforcement: If the counter exceeds the limit, the system raises RateLimitException, returning HTTP 429 with a retry-after header. Otherwise, the request proceeds.

Summary

  • Database-Driven Configuration: Store tiers in src/app/models/tier.py and rate limits in src/app/models/rate_limit.py to enable dynamic updates without redeployment.
  • Redis-Backed Enforcement: The RateLimiter class in src/app/core/utils/rate_limit.py provides distributed, sliding-window counting using Redis.
  • Dependency Injection: Use rate_limiter_dependency from src/app/api/dependencies.py to protect routes with automatic tier resolution.
  • Flexible Fallbacks: Default limits in src/app/core/config.py ensure baseline protection when specific rules are undefined.
  • Tier Differentiation: Configure divergent limits for free and pro users on the same endpoints by creating separate rate_limit records for each tier-path pair.

Frequently Asked Questions

How do I create new tiers beyond free and pro?

Create additional tiers by posting to the tiers endpoint with any name (e.g., enterprise, basic). The system in src/app/models/tier.py supports arbitrary tier names. Once created, assign rate limits to the new tier_id via the rate limit CRUD endpoints just as you would for free or pro tiers.

What happens if no rate limit rule exists for a specific tier and path?

The rate_limiter_dependency falls back to the default values defined in src/app/core/config.py: DEFAULT_RATE_LIMIT_LIMIT and DEFAULT_RATE_LIMIT_PERIOD. This ensures every protected endpoint has rate limiting even without explicit per-path configuration.

Can I disable rate limiting for specific endpoints?

Yes. Simply omit Depends(rate_limiter_dependency) from the endpoint or router definition. Public endpoints without this dependency bypass the Redis counter entirely and allow unlimited requests, regardless of user tier.

How does the Redis sliding window algorithm work?

The RateLimiter utility uses time-windowed counters where the Redis key includes a timestamp bucket (window_start) based on the current period. Each request increments the counter for the active window, and the system rejects requests when the count exceeds the configured limit for that tier-path combination.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →