Management API
The gateway's scoped management API -- key lifecycle, spend against the governing policy, and the routable model catalog.
Management API
Alongside the inference surface, the gateway serves a small, fully-typed
management API for key lifecycle, usage and model discovery. It is mounted under
/api/v1/ on the main listener, authenticated with a management-kind issued
key (a runtime key is refused with a 403), and each operation declares the
scope it requires.
The management API is dormant unless you enable it. On a self-hosted
gateway it requires MGMT_API_ENABLED=true, a configured AUTH_TOKEN, and
issued-key handling switched on (GOVERNANCE_ISSUED_KEYS=true). See
Self-hosting.
Authentication and scopes
One credential type covers inference and management. Scopes are a property of the issued key, set when the key is created:
| Scope | Grants |
|---|---|
keys:read | Listing and retrieving runtime keys. |
keys:write | Creating, updating and revoking runtime keys. |
usage:read | Reading spend against the governing policy. |
models:read | Reading the routable model catalog. |
A key issued with no scopes holds all four. Scope a management key deliberately if you want it narrow.
Keys
| Operation | Route | Scope |
|---|---|---|
| List runtime keys | GET /api/v1/keys | keys:read |
| Create a runtime key | POST /api/v1/keys | keys:write |
| Retrieve a runtime key | GET /api/v1/keys/{key_id} | keys:read |
| Update a runtime key | PATCH /api/v1/keys/{key_id} | keys:write |
| Revoke a runtime key | DELETE /api/v1/keys/{key_id} | keys:write |
Listing takes offset and limit query parameters and returns next_offset.
A key resource carries id, name, kind, disabled, expires_at,
last_used_at, created_at, updated_at and policy_id.
Creation returns the plaintext token exactly once. POST /api/v1/keys
responds 201 with the key resource plus a key field holding the token.
There is no later route that will hand it to you again.
Creating a key requires a name, and optionally takes expires_at and
policy_id. A PATCH is a partial update -- absent fields are left unchanged --
and may set name, disabled, expires_at or policy_id -- but metadata
fields and policy_id cannot be combined in one request, and an empty patch is
a 400. Business
validation (name rules, whether the policy exists, the issued-key governing-policy
invariant) is the control plane's; the route shape is only the transport.
Usage
| Operation | Route | Scope |
|---|---|---|
| A key's spend against its governing policy | GET /api/v1/keys/{key_id}/usage | usage:read |
| Any principal's spend -- an issued key, a linked identity, or the unattributed bucket | GET /api/v1/usage?principal_kind=&principal_ref= | usage:read |
The response reports the governing policy, whether it is active, the counter scope and whether it is pooled, the limit, the window, the spend in the current bucket, the principal's spend and whether that figure is complete, and the remaining allowance -- all in micro-USD where they are money.
Every value that does not apply, or is not yet known, is a literal JSON
null -- never a zero. A 0 in this response would read as "spent
nothing", which is a different and much more reassuring claim than "we do not
know".
Models
| Operation | Route | Scope |
|---|---|---|
| The routable model catalog | GET /api/v1/models | models:read |
Paged with page_token / next_page_token. Each model carries its
author/slug id, the provider, input, output and cache-read prices, the context
window, and benchmark scores where they are published (mmlu, human_eval,
mt_bench, avg_score).
Prices are objects with a currency code, units and nanos. A price that is not
published is null rather than zero: an unpublished component is not a free
one.
Contract versioning
The published contract carries its own semver clock, bumped in the same change as any enforced difference -- routes, override fields, refused fields, headers, or management scopes. Loosening or adding is a minor version; tightening is a major, and is intended to be close to never.
The contract is also held against the implementation by a parity test asserting five equivalences: the routes match the keyed allowlist, the override schema matches the decoder, the refused-field schemas match the refused-field list, the header parameters match the read-and-strip list, and the management operations and scopes match the management router. If those disagree, the contract is what moves.