API Keys

How to mint, list, disable, and delete tenant API keys, and how obleth stores them securely.

API keys are the credentials clients use to authenticate with the obleth data plane. Every key belongs to exactly one tenant and inherits that tenant's weight, quota, and fairshare group.

How obleth stores keys

obleth never stores the raw secret. When a key is created:

  1. A cryptographically random secret is generated (sk_ + 48 random hex characters).
  2. sha256(secret) is stored in Postgres (api_keys.key_hash).
  3. A display-only prefix (e.g. sk_a1b2c3d4e5f6a1b) is stored for dashboards.
  4. The ResolvedKey (tenant details + weight + quota) is synced to Redis at obleth:key:{hash}.

The raw secret is returned once in the creation response. If you lose it, the only option is to delete the key and create a new one.

Creating a key

curl -X POST http://localhost:9090/api/v1/tenants/$TID/keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "prod"}'

Response:

{
  "key": {
    "id": "...",
    "tenant_id": "...",
    "name": "prod",
    "key_prefix": "sk_a1b2c3d4e5f6a1b",
    "disabled": false,
    "created_at": "2026-01-15T10:00:00Z"
  },
  "secret": "sk_a1b2c3d4e5f6..."
}

Listing keys for a tenant

# All keys for a specific tenant
curl http://localhost:9090/api/v1/tenants/$TID/keys \
  -H "Authorization: Bearer $TOKEN"

# All keys across all tenants (with optional filters)
curl "http://localhost:9090/api/v1/keys?limit=50" \
  -H "Authorization: Bearer $TOKEN"

Disabling a key

Disabling a key immediately prevents it from authenticating without deleting the key's audit history:

curl -X PUT http://localhost:9090/api/v1/keys/$KEY_ID/disabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"disabled": true}'

The change is synced to Redis and invalidated in moka across all pods. The key returns 403 api key disabled immediately on the next request.

Re-enable:

curl -X PUT http://localhost:9090/api/v1/keys/$KEY_ID/disabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"disabled": false}'

Deleting a key

curl -X DELETE http://localhost:9090/api/v1/keys/$KEY_ID \
  -H "Authorization: Bearer $TOKEN"

This removes the key from Postgres and Redis. The audit log retains the deletion event. Any client using the deleted key will receive 401 invalid api key.

Key rotation best practice

To rotate a key with zero downtime:

  1. Create a new key: POST /api/v1/tenants/$TID/keys
  2. Update your application to use the new secret.
  3. Verify the new key works.
  4. Delete the old key: DELETE /api/v1/keys/$OLD_KEY_ID

Don't use disable+enable for rotation — just create a new one and delete the old.

Multiple keys per tenant

A tenant can have many keys. All keys for the same tenant share the same weight and quota. Useful patterns:

  • One key per environment: prod, staging, ci
  • One key per application: chatbot, search, summarizer
  • One key per customer if you're building a multi-tenant product on top of obleth

Usage in ClickHouse is recorded per key_id, so you can break down cost by key even within the same tenant.