FastAPI Example
A complete FastAPI Todo API protected with apikee.
FastAPI Example
A complete Todo API demonstrating SecuredFastAPI, ApikeeDepends, and scope enforcement.
Run it
pip install "apikee[fastapi]" uvicorn
uvicorn main:app --reloadOpen http://localhost:8000/docs — every endpoint has a 🔒 lock.
Full source
from datetime import timedelta
from typing import Optional
from fastapi import Depends, Request
from fastapi.responses import JSONResponse
from apikee.fastapi import SecuredFastAPI, ApikeeDepends, require_scope
from apikee import ApikeeClaims
app = SecuredFastAPI(
title="apikee Todo API",
version="1.0.0",
secrets=["local-dev-secret-change-in-production"],
exclude_paths={"/health", "/docs", "/redoc", "/openapi.json", "/keys"},
# server_key="sk_live_...",
# project_env="my-app-production",
)
todos: dict[str, list[dict]] = {}
@app.get("/health", tags=["system"])
def health():
return {"status": "ok"}
@app.post("/keys", tags=["auth"], summary="Issue a new API key")
def create_key(tenant: str, scopes: str = "read,write", plan: str = "free"):
key = app.apikee.create(
tenant=tenant,
scopes=scopes.split(","),
expires_in=timedelta(days=90),
meta={"plan": plan},
)
return {"key": key, "tenant": tenant, "scopes": scopes.split(",")}
@app.get("/me", tags=["auth"])
def me(claims: ApikeeClaims = ApikeeDepends()):
return {"id": claims.id, "tenant": claims.tenant, "scopes": claims.scopes}
@app.get("/todos", tags=["todos"])
def list_todos(claims: ApikeeClaims = ApikeeDepends()):
return {"tenant": claims.tenant, "todos": todos.get(claims.tenant, [])}
@app.post("/todos", tags=["todos"])
def create_todo(
title: str,
description: Optional[str] = None,
claims: ApikeeClaims = ApikeeDepends(),
):
if "write" not in claims.scopes:
return JSONResponse({"error": "INSUFFICIENT_SCOPE"}, 403)
bucket = todos.setdefault(claims.tenant, [])
todo = {"id": len(bucket) + 1, "title": title, "description": description, "done": False}
bucket.append(todo)
return todo
@app.delete("/todos/{todo_id}", tags=["todos"],
dependencies=[Depends(require_scope("admin"))])
def delete_todo(todo_id: int, claims: ApikeeClaims = ApikeeDepends()):
"""Requires admin scope."""
bucket = todos.get(claims.tenant, [])
todos[claims.tenant] = [t for t in bucket if t["id"] != todo_id]
return {"deleted": todo_id}Try it
Issue a key
curl -X POST "http://localhost:8000/keys?tenant=acme&scopes=read,write"
export KEY="apikee_..."Use the key
curl -H "x-api-key: $KEY" http://localhost:8000/todos
curl -X POST -H "x-api-key: $KEY" "http://localhost:8000/todos?title=Buy+milk"
curl -H "x-api-key: $KEY" http://localhost:8000/meTest scope enforcement
# Issue read-only key
export RO_KEY=$(curl -s -X POST "http://localhost:8000/keys?tenant=bob&scopes=read" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
# 403 — needs write
curl -X POST -H "x-api-key: $RO_KEY" "http://localhost:8000/todos?title=Fail"
# 403 — needs admin
curl -X DELETE -H "x-api-key: $KEY" http://localhost:8000/todos/1
