UsageRail: API keys, rate limits and per-customer quotas for a Django API, as one middleware

Hi all,

If you sell access to a Django API, you usually end up writing the same pieces: issue API keys, rate limit each customer, stop them at their monthly quota, and count usage so you can bill it. I’ve been building UsageRail to cover that, and the Python SDK now ships a Django middleware.

# settings.py
MIDDLEWARE = [
    # ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "usagerail.django.UsageRailMiddleware",
]

USAGERAIL = {
    "app_key": os.environ["USAGERAIL_APP_KEY"],
    "auth": "api-key",
}

The middleware checks the key, the rate limit and the quota before your view runs, and counts the request once the response is closed. If you bill by something other than requests, such as tokens, you can count units from the view:

def generate(request):
    result = run_model(request.body)
    request.usagerail.track(result.tokens)
    return JsonResponse(result.body)

Requests are counted under the URL pattern that matched, such as /v1/items/<int:pk>, not the raw path. 2xx and 3xx responses count; 4xx, 5xx and refused requests don’t. The SDK needs Python 3.10+, has no dependencies of its own and is MIT licensed (pip install usagerail). Requests never go through our servers. Keys and counts sync with the hosted service in the background, and billing runs through the seller’s own Stripe account.

A few Django-specific things I ran into while building it:

  • DRF’s TokenAuthentication runs inside the view, after the middleware. If you want to identify customers by your own DRF tokens, the consumer function has to call the authentication class itself, or every request looks anonymous.
  • DRF routers register their URLs as regular expressions, so /v1/items/(?P<pk>[^/.]+)/$ is the template you get.
  • Under uvicorn, Django doesn’t handle the ASGI lifespan shutdown, so the counts still waiting to be sent were lost on every restart. There’s now an asgi() wrapper for asgi.py that sends them before the process exits. Gunicorn doesn’t need it.

Docs for the Django setup, including the DRF notes: Django Rate Limiting and API Key Middleware | UsageRail

It’s early (0.1.1), and I’d like to hear how people here handle this today, whether that’s DRF throttling plus your own counters, django-ratelimit or an API gateway in front. What did you have to build yourself?

Thanks,
Super