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.
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)$ 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 countedTry 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.
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.
- Create a keyFrom Python, the CLI, the admin API or the dashboard. You see the plaintext once.
- Client sends itIn the
X-API-Keyheader. The gate looks up the hash. - Gate checksValid, not revoked or expired, within its rate limit. Otherwise 401, 403 or 429.
- 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.