Async SDK for issuing and validating scoped API keys
Project description
apikeys-platform
Async Python SDK for issuing, scoping, and validating API keys — backed by SQLite or PostgreSQL.
Designed for developers who want to add API key management to their own product without building the infrastructure from scratch.
Installation
# SQLite (development / small deployments)
pip install "apikeys-platform[sqlite]"
# PostgreSQL (production)
pip install "apikeys-platform[postgresql]"
# MySQL (production)
pip install "apikeys-platform[mysql]"
# All drivers
pip install "apikeys-platform[all]"
Requires Python 3.11+.
Quickstart
create_tables and APIKeyClient both accept plain URLs — no need to specify the async driver suffix:
import asyncio
from apikeys import APIKeyClient, KeyMetadata, create_tables
async def main():
# SQLite for dev: "sqlite:///myapp.db"
# PostgreSQL prod: "postgresql://user:pass@host:5432/dbname"
# MySQL prod: "mysql://user:pass@host:3306/dbname"
db_url = "sqlite:///myapp.db"
await create_tables(db_url) # creates tables on first run; safe to call on every restart
client = APIKeyClient(db_url)
# One-time setup
org = await client.create_organization("Acme Inc")
product = await client.create_product(str(org.id), "My API")
project = await client.create_project(str(org.id), "v1")
await client.add_product_to_project(str(product.id), str(project.id))
# Issue a key for one of your users
result = await client.create_key(
str(org.id),
project_id=str(project.id),
product_id=str(product.id),
metadata=KeyMetadata(
name="Alice's key",
scopes=["read", "write"],
rate_limit=1000,
custom={"user_id": "u_alice", "plan": "pro"},
),
)
print(result.plaintext) # return this once to your user
# Validate an incoming request
key = await client.validate_key(
result.plaintext,
product_id=str(product.id),
required_scope="read",
)
print(key.metadata.custom["user_id"]) # → u_alice
asyncio.run(main())
Key concepts
| Concept | Description |
|---|---|
| Organization | Top-level tenant — maps to one of your customers or your own company |
| Project | Groups keys within an org (e.g. v1, v2, mobile) |
| Product | A named API surface linked to projects; keys can be scoped to one product |
KeyMetadata.custom |
Arbitrary JSON attached to a key — store user_id, plan, tenant_id, etc. |
Keys are scoped from broad → narrow: org-wide → project-scoped → product-scoped.
validate_key() checks the key is active, matches the expected product, and holds the required scope.
Exceptions
from apikeys import InvalidKeyError, RevokedKeyError, InsufficientScopeError
| Exception | When raised |
|---|---|
InvalidKeyError |
Key not found |
RevokedKeyError |
Key exists but has been revoked |
InsufficientScopeError |
Key is valid but missing the required scope |
Full example
End-to-end script showing one-time setup, per-user key creation, request validation, and listing keys by user:
import asyncio
from apikeys import APIKeyClient, KeyMetadata
from apikeys.db.session import create_tables
async def main() -> None:
DB_URL = "sqlite+aiosqlite:///acme_demo.db"
await create_tables(DB_URL)
client = APIKeyClient(DB_URL)
# ── One-time setup ───────────────────────────────────────────────────────
org = await client.create_organization("Acme Corp")
product = await client.create_product(str(org.id), "Acme App")
project = await client.create_project(str(org.id), "Acme API v1")
await client.add_product_to_project(str(product.id), str(project.id))
ORG_ID = str(org.id)
PRODUCT_ID = str(product.id)
PROJECT_ID = str(project.id)
# ── Issue a key for a user ───────────────────────────────────────────────
result = await client.create_key(
ORG_ID,
project_id=PROJECT_ID,
product_id=PRODUCT_ID,
metadata=KeyMetadata(
name="Alice's production key",
scopes=["read", "write"],
rate_limit=1000,
custom={"user_id": "u_alice", "plan": "pro"},
),
)
print(f"plaintext: {result.plaintext}") # return this ONCE to the user
# ── Validate an incoming request ─────────────────────────────────────────
key = await client.validate_key(
result.plaintext,
product_id=PRODUCT_ID,
required_scope="read",
)
print(f"user: {key.metadata.custom['user_id']} plan: {key.metadata.custom['plan']}")
# ── List a user's keys ───────────────────────────────────────────────────
all_keys = await client.list_project_keys(PROJECT_ID)
alice_keys = [k for k in all_keys if k.metadata.custom.get("user_id") == "u_alice"]
for k in alice_keys:
print(f" {k.metadata.name} [{k.id}] revoked={k.revoked_at is not None}")
asyncio.run(main())
Source: examples/acme_integration.py
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file apikeys_platform-0.1.2.tar.gz.
File metadata
- Download URL: apikeys_platform-0.1.2.tar.gz
- Upload date:
- Size: 9.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7cd7b9f6115d810dbda8021adb7ef409321e87768ee54828c62b229390ac935f
|
|
| MD5 |
77fe7cabbdcdcc1d1fee5afc2379c449
|
|
| BLAKE2b-256 |
4207259a62af94235f4a73f9805175aa85ffefc42bff512adf7f9e8d646678ed
|
File details
Details for the file apikeys_platform-0.1.2-py3-none-any.whl.
File metadata
- Download URL: apikeys_platform-0.1.2-py3-none-any.whl
- Upload date:
- Size: 9.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
993cb19dfb3d62c5d6727497351baffba3ca8ebe2d3699d3212cc139bb0c50fc
|
|
| MD5 |
72c2649ad7c46910fc1ad2c122772eb5
|
|
| BLAKE2b-256 |
09142244e6f5305c420199839e56fcf4741639962bf5763da7955131326a171d
|