Skip to content

Admin Console — API Keys

CRUD operations for the API keys that gate /v1/*. All endpoints require an authenticated session (and CSRF on writes).

Key object

{
  "id": 1,
  "key": "deadbeef...",            // opaque string, treat as a secret
  "name": "claude-code-laptop",     // human label, ≤ 120 chars
  "is_active": true,                 // bool; inactive keys are 401'd at the proxy
  "created_at": "2026-04-25T07:14:33+00:00",
  "last_used_at": "2026-04-26T01:02:11+00:00",  // null if never used; coalesced ~1/min
  "total_request_count": 1234,
  "total_input_tokens": 9876543,
  "total_output_tokens": 1234567
}

last_used_at is updated at most once per minute per key (write coalescing). Token totals are incremented on each response close, so they reflect successful billable requests only.

GET /api/keys

List all keys.

Auth Session
CSRF Not required (read)

Returns [Key, ...] ordered by id ascending.

POST /api/keys

Mint a new key.

Auth Session
CSRF Required

Request

{
  "name": "claude-code-laptop",     // required, 1..120 chars
  "key":  "<optional explicit key>" // optional; defaults to uuid4().hex
}

Responses

Status Body
201 full Key object
400 {"error": "name is required"} / name must be a string / name must not be empty / name must be at most 120 characters / key must be a non-empty string
409 {"error": "Key already exists"} (if explicit key collides)

When key is omitted, the server generates a random hex string via uuid.uuid4().hex (32 chars, ~128 bits of entropy). It is returned once in the response body; the server never returns it again — the admin UI shows a copy-button on key creation specifically for that reason.

PATCH /api/keys/<id>

Update a key's name and/or is_active.

Auth Session
CSRF Required

Request

At least one field must be present:

{
  "name": "renamed",        // optional, same validation as POST
  "is_active": false         // optional, must be boolean
}

Responses

Status Body
200 full Key object (post-update)
400 validation error / "No fields to update"
404 {"error": "Key not found"}

Setting is_active: false immediately invalidates the key on subsequent /v1/* requests — there is no client-side reconnection grace.

DELETE /api/keys/<id>

Hard-delete the key row. Usage history attached to the key (in daily_usage and usage_buckets) is not cascaded by application code; the SQLite schema does not declare ON DELETE CASCADE on those tables.

Auth Session
CSRF Required

Responses

Status Body
200 {"ok": true}
404 {"error": "Key not found"}

Operational notes

  • Keys are stored verbatim in SQLite at proxy.db (no hash). The database is only readable by the glm-proxy service user; full-disk encryption + EBS-volume isolation are the layer protecting them.
  • Deactivating a key is preferred to deleting it — the row keeps the historical totals visible for billing / audit.
  • The admin UI surfaces totals from total_input_tokens / total_output_tokens directly; for time-bucketed views see Usage Statistics.