Keygate
Alpha, v0.5 on PyPI

API keys for FastAPI, in a few lines.

Authentication, per-key rate limits and usage tracking as one FastAPI dependency. SQLite by default, so there is nothing to set up.

pip install fastapi-keygate

The whole quickstart

Protect a route, mint a key, and the limit is enforced per key. You import keygate; the PyPI package is fastapi-keygate.

app.py
from fastapi import Depends, FastAPI
from keygate import ApiKey, KeyGate

app = FastAPI()
gate = KeyGate(app, admin_secret="change-me")  # SQLite, zero setup

@app.get("/hello")
def hello(key: ApiKey = Depends(gate.require)):
    return {"hello": key.owner_id}

if not gate.list_keys():  # demo only: mint a first key
    print("Your API key:", gate.create_key("demo-tenant", per_minute=5).key)
terminal
$ uvicorn app:app
Your API key: kg_...
INFO:     Uvicorn running on http://127.0.0.1:8000

$ curl -i localhost:8000/hello
HTTP/1.1 401 Unauthorized # no key

$ KEY=kg_... # the key the app printed
$ curl -H "X-API-Key: $KEY" localhost:8000/hello
{"hello":"demo-tenant"}

$ for i in 1 2 3 4; do curl -s -o /dev/null -w "%{http_code}\n" -H "X-API-Key: $KEY" localhost:8000/hello; done
200
200
200
200 # 5 requests so far, the limit is 5 per minute

$ curl -i -H "X-API-Key: $KEY" localhost:8000/hello
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>

$ keygate usage 1 # see what was counted

Try the rate limit

A simulation running in your browser: a key limited to 5 requests per minute, like the quickstart. No request is sent anywhere.

GET /helloSimulation
5 of 5 remaining

Last response: none yet

What you get

Small surface, the parts most APIs end up hand-writing.

Hashed, shown once

Only a SHA-256 hash is stored. The plaintext key is returned once, at creation. Keys are read from X-API-Key or Authorization: Bearer, never from query strings.

Per-key rate limits

Requests per minute and/or hour, stored on each key. Over the limit returns 429 with Retry-After; responses carry X-RateLimit-* headers.

Usage tracking

Every authenticated request is logged with timestamp, method, route and status. Query totals by status and endpoint, and prune old rows.

Rotation with a grace period

gate.rotate(id, grace=timedelta(hours=24)) issues a replacement; the old key keeps working until the grace period ends.

Scopes and expiry

Guard routes with gate.require_scopes("write") (missing scope is 403). Keys can expire with expires_in=.

Sync and async

KeyGate for sync apps, AsyncKeyGate for async SQLAlchemy drivers, with the same features.

Web dashboard

Browse keys, usage charts and live rate-limit status; edit, rotate or revoke. Mount it with mount_dashboard(app, gate).

Opt-in plans

Optional per-owner plans cap key counts, per-key limits and scopes. Off unless you pass plans=. Billing is not included.

CLI included

keygate create, list, rotate, revoke, usage and prune work without writing code.

How it works

Limits belong to the key, so they are enforced inside the auth dependency.

  1. Create a keyFrom Python, the CLI, the admin API or the dashboard. You see the plaintext once.
  2. Client sends itIn the X-API-Key header. The gate looks up the hash.
  3. Gate checksValid, not revoked or expired, within its rate limit. Otherwise 401, 403 or 429.
  4. Request is loggedYour route runs and the usage row records route and status.

A dashboard, built in

Sign in with your admin secret. The live demo runs on seeded sample data.

Illustration only. Open the real thing: sample data, enter any value to sign in.

Status

Keygate is alpha (0.x). The API may change between minor versions. It is MIT licensed.

Not included yet: database migrations (tables are created on startup). With several workers, use a Redis limits store, since the default is per process. Feedback and bug reports are welcome as GitHub issues.