67 docs indexed

Authentication

API key format, hash method, and how to authenticate against the data plane and Management API.

Data plane

All data-plane requests must include a tenant API key:

Authorization: Bearer sk_<48 hex chars>

Example:

curl http://localhost/v1/chat/completions \
  -H "Authorization: Bearer sk_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{"model": "my-model", "messages": [...]}'

Key format

ComponentValue
Prefixsk_
Secret part48 hex characters (24 random bytes)
Total length51 characters
Display prefixFirst 18 characters

Key storage

obleth never stores the raw key. On creation:

  1. The gateway generates 24 cryptographically random bytes.
  2. It hex-encodes them and prepends sk_.
  3. It computes SHA-256(full_secret_bytes) to produce the key hash.
  4. It stores the hash and the display prefix in Postgres (api_keys.key_hash, api_keys.key_prefix) and caches the resolved key in Redis under obleth:key:{hex_hash}.
  5. It returns the full secret to the caller once.

On each request, the gateway re-hashes the incoming bearer token and looks the hash up in its in-process cache first, then Redis. The data plane never reads Postgres on the hot path: Redis is populated by the Management API on every mutation and re-warmed from Postgres when a gateway process starts.

Optional hash pepper

If OBLETH_API_KEY_PEPPER is set, it is mixed into the hash so a leaked config database alone can't be used to confirm guessed keys without also stealing the pepper (which lives out of band). When unset, hashing is byte-for-byte identical to the unpeppered scheme, so existing keys keep working. Changing or adding a pepper invalidates all previously issued keys — they must be rotated.

Management API

All requests to the Management API (:9180) must include the admin bearer token:

Authorization: Bearer <OBLETH_ADMIN_TOKEN>
curl http://localhost:9180/api/v1/tenants \
  -H "Authorization: Bearer $OBLETH_ADMIN_TOKEN"

OBLETH_ADMIN_TOKEN has no default and is compared in constant time. There is no per-user RBAC for the Management API — the admin token is all-or-nothing. Protect it accordingly; see the Security guide.

Three routes are public and need no token: GET /api/v1/health, GET /api/v1/version, and GET /api/v1/openapi.json. Every other route returns 401 without a matching bearer token.

Creating a key

curl -X POST http://localhost:9180/api/v1/tenants/$TENANT_ID/keys \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app-key"}'

Response:

{
  "key": {
    "id": "...",
    "tenant_id": "...",
    "name": "my-app-key",
    "key_prefix": "sk_a1b2c3d4e5f6a1b",
    "disabled": false
  },
  "secret": "sk_a1b2c3d4e5f6..."
}

Key lifecycle

ActionEndpoint
CreatePOST /api/v1/tenants/{id}/keys
ListGET /api/v1/keys
Update name, description, and per-key budgetPUT /api/v1/keys/{id}
DisablePUT /api/v1/keys/{id}/disabled with {"disabled": true}
Enable or disable span tracingPUT /api/v1/keys/{id}/tracing
DeleteDELETE /api/v1/keys/{id}

A disabled key returns 403 Forbidden. A deleted key returns 401 Unauthorized.