Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.limitly.dev/llms.txt

Use this file to discover all available pages before exploring further.

The Limitly Python SDK provides middleware functionality to easily add rate limiting to your Python web applications.

FastAPI Middleware

Create middleware for FastAPI applications:
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from limitly import Limitly

app = FastAPI()
limitly = Limitly(api_key=os.getenv("LIMITLY_API_KEY"))

@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
    api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
    
    if not api_key:
        return JSONResponse(
            status_code=401,
            content={"error": "API Key required"}
        )
    
    result = limitly.validation.validate(api_key, request.url.path, request.method)
    
    if not result.success:
        return JSONResponse(
            status_code=429,
            content={
                "error": "Rate limit exceeded",
                "details": result.details
            }
        )
    
    response = await call_next(request)
    return response

@app.get("/api/users")
async def get_users():
    return {"message": "Users retrieved successfully"}

Flask Middleware

Create middleware for Flask applications:
from flask import Flask, request, jsonify
from limitly import Limitly

app = Flask(__name__)
limitly = Limitly(api_key=os.getenv("LIMITLY_API_KEY"))

@app.before_request
def rate_limit_middleware():
    # Skip middleware for certain paths
    if request.path.startswith('/health'):
        return
    
    api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
    
    if not api_key:
        return jsonify({"error": "API Key required"}), 401
    
    result = limitly.validation.validate(api_key, request.path, request.method)
    
    if not result.success:
        return jsonify({
            "error": "Rate limit exceeded",
            "details": result.details
        }), 429

@app.route("/api/users", methods=["GET"])
def get_users():
    return jsonify({"message": "Users retrieved successfully"})

Django Middleware

Create middleware for Django applications:
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from limitly import Limitly

class RateLimitMiddleware(MiddlewareMixin):
    def __init__(self, get_response):
        super().__init__(get_response)
        self.limitly = Limitly(api_key=os.getenv("LIMITLY_API_KEY"))
    
    def process_request(self, request):
        # Skip middleware for certain paths
        if request.path.startswith('/admin/'):
            return None
        
        api_key = request.headers.get("Authorization", "").replace("Authorization ", "")
        
        if not api_key:
            return JsonResponse(
                {"error": "API Key required"}, 
                status=401
            )
        
        result = self.limitly.validation.validate(api_key, request.path, request.method)
        
        if not result.success:
            return JsonResponse({
                "error": "Rate limit exceeded",
                "details": result.details
            }, status=429)
        
        return None

# Add to MIDDLEWARE in settings.py
MIDDLEWARE = [
    # ... other middleware
    'your_app.middleware.RateLimitMiddleware',
]

Custom Middleware Options

Configure middleware with custom options:
from functools import wraps
from flask import request, jsonify

def create_rate_limit_middleware(limitly, options=None):
    def rate_limit_middleware(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
            
            if not api_key:
                return jsonify({"error": "API Key required"}), 401
            
            result = limitly.validation.validate(
                api_key, 
                request.path, 
                request.method,
                options=options
            )
            
            if not result.success:
                return jsonify({
                    "error": "Rate limit exceeded",
                    "details": result.details
                }), 429
            
            return f(*args, **kwargs)
        return decorated_function
    return rate_limit_middleware

# Use custom middleware
rate_limit = create_rate_limit_middleware(limitly, {"timeout": 5})

@app.route("/api/users", methods=["GET"])
@rate_limit
def get_users():
    return jsonify({"message": "Users retrieved successfully"})

Error Handling

Handle different types of errors in your middleware:
from limitly import Limitly, LimitlyError

def rate_limit_middleware(request):
    try:
        api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
        
        if not api_key:
            return {"error": "API Key required"}, 401
        
        result = limitly.validation.validate(api_key, request.path, request.method)
        
        if not result.success:
            return {
                "error": "Rate limit exceeded",
                "details": result.details
            }, 429
        
        return None  # Continue to next middleware/handler
        
    except LimitlyError as e:
        return {"error": f"Validation error: {e}"}, 400
    except Exception as e:
        return {"error": f"Unexpected error: {e}"}, 500

Multiple API Key Formats

Handle multiple API key formats in your middleware:
def extract_api_key(request):
    """Extract API key from various sources"""
    # Check Authorization header
    auth_header = request.headers.get("Authorization", "")
    if auth_header.startswith("Bearer "):
        return auth_header[7:]
    
    # Check X-API-Key header
    api_key = request.headers.get("X-API-Key")
    if api_key:
        return api_key
    
    # Check query parameter
    api_key = request.args.get("api_key")
    if api_key:
        return api_key
    
    return None

def rate_limit_middleware(request):
    api_key = extract_api_key(request)
    
    if not api_key:
        return {"error": "API Key required"}, 401
    
    result = limitly.validation.validate(api_key, request.path, request.method)
    
    if not result.success:
        return {
            "error": "Rate limit exceeded",
            "details": result.details
        }, 429
    
    return None

Testing Middleware

Test your middleware with different scenarios:
import os
from limitly import Limitly

def test_middleware():
    limitly = Limitly(api_key=os.getenv("LIMITLY_API_KEY"))
    
    # Mock request object
    class MockRequest:
        def __init__(self, headers, path, method):
            self.headers = headers
            self.path = path
            self.method = method
    
    # Test with valid API key
    request = MockRequest(
        headers={"Authorization": "Bearer valid_api_key"},
        path="/api/test",
        method="GET"
    )
    
    result = rate_limit_middleware(request)
    print(f"Valid key result: {result}")
    
    # Test with invalid API key
    request = MockRequest(
        headers={"Authorization": "Bearer invalid_api_key"},
        path="/api/test",
        method="GET"
    )
    
    result = rate_limit_middleware(request)
    print(f"Invalid key result: {result}")

if __name__ == "__main__":
    test_middleware()

CLI Testing

Test middleware using the CLI tool:
# Test middleware with different scenarios
limitly validate --api-key valid_key --path /api/users --method GET
limitly validate --api-key invalid_key --path /api/users --method GET
limitly validate --api-key limited_key --path /api/users --method GET

Performance Considerations

Optimize your middleware for performance:
import time
from functools import lru_cache

# Cache API key validation results
@lru_cache(maxsize=1000)
def cached_validate(api_key, path, method):
    return limitly.validation.validate(api_key, path, method)

def optimized_middleware(request):
    api_key = request.headers.get("Authorization", "").replace("Bearer ", "")
    
    if not api_key:
        return {"error": "API Key required"}, 401
    
    # Use cached validation for better performance
    result = cached_validate(api_key, request.path, request.method)
    
    if not result.success:
        return {
            "error": "Rate limit exceeded",
            "details": result.details
        }, 429
    
    return None

Next Steps