# Admin API reference

> Every admin route, grouped by area. Exact request/response shapes are in the OpenAPI spec.

URL: https://boelabs.com/en/docs/ai-gateway/reference-admin-api
Language: English

All `/admin/*` routes require an operator identity: `Authorization: Bearer $MASTER_KEY`, or — when
`DASH_ENABLED` — an operator session cookie whose role carries the permission that route needs (the
master key carries all of them). See [Security → Roles](https://boelabs.com/docs/ai-gateway/security#roles). Success responses use
`{ "data": ... }`; paginated lists use `{ "data": [...], "pagination": { limit, offset, total, nextOffset } }`.

Any `POST /admin/*` accepts an optional `Idempotency-Key` header — see
[Idempotent creates](#idempotent-creates).
This page is a map of what exists; for exact request/response bodies, see
[`apps/api/openapi.yaml`](https://github.com/boelabs/ai-gateway/blob/main/apps/api/openapi.yaml)
or the interactive spec (see [API guide](https://boelabs.com/docs/ai-gateway/api-overview)).

## Typed clients

Every management response carries a schema in
[`apps/api/openapi.yaml`](https://github.com/boelabs/ai-gateway/blob/main/apps/api/openapi.yaml),
so a fully typed client is generated rather than hand-written:

```bash
bunx openapi-typescript apps/api/openapi.yaml -o src/api/schema.d.ts
```

Pair it with a typed fetch wrapper (`openapi-fetch` or equivalent) and paths, query parameters,
request bodies and response shapes are all checked at compile time.

The spec is generated from the same Zod schemas the API validates with, and two tests fail the build
if it drifts: one when the committed YAML is stale, and one when a management response is documented
without a schema. There is no second, private protocol for our own UI to use — the documented API is
the API.

## Deployments

| Route | Purpose |
|---|---|
| `GET /admin/operations` | Available operations, required credentials, and transports per adapter. |
| `POST /admin/deployments/resolve` | Dry-run: resolve effective capabilities/operations/transports for a body, without saving. |
| `GET /admin/deployments` | List deployments. Query: `enabled`, `publicModel`, `q` (search name/prefix). |
| `POST /admin/deployments` | Create a deployment (see [Creating deployments](https://boelabs.com/docs/ai-gateway/creating-deployments)). |
| `GET /admin/deployments/:id` | Read one deployment. |
| `PATCH /admin/deployments/:id` | Update a deployment. |
| `DELETE /admin/deployments/:id` | Delete a deployment. |

## Virtual keys

| Route | Purpose |
|---|---|
| `GET /admin/keys` | List keys. Query: `limit`, `offset`, `enabled`, `publicModel`, `q` (search name/prefix). |
| `POST /admin/keys` | Create a virtual key (see [Virtual keys](https://boelabs.com/docs/ai-gateway/virtual-keys)). |
| `PATCH /admin/keys/:id` | Update scope, limits, budget, or enabled state; send `null` to clear a limit. `{"private": true}` erases the stored secret. |
| `GET /admin/keys/:id/secret` | Copy a key again (`keys:write`, audited). See [Copy a key again](https://boelabs.com/docs/ai-gateway/virtual-keys#copy-a-key-again). |
| `POST /admin/keys/:id/rotate` | New secret for the same key; the old one stops working immediately. Body optional: `{"private": bool}`. |
| `DELETE /admin/keys/:id` | Permanently delete a key. |

## Router and fallbacks

| Route | Purpose |
|---|---|
| `GET /admin/router-settings` | Effective router configuration (defaults if never set). |
| `PUT /admin/router-settings` | Patch any subset of routing/parameter-policy/retry settings — see [Routing](https://boelabs.com/docs/ai-gateway/routing) and [Parameter policy](https://boelabs.com/docs/ai-gateway/parameter-policy). |
| `GET /admin/fallbacks` | List fallback chains. |
| `PUT /admin/fallbacks` | Upsert a chain (`primaryModel`, `fallbackModels`, optional `reason`). |
| `DELETE /admin/fallbacks/:primaryModel/:reason` | Remove a chain. See [Fallbacks](https://boelabs.com/docs/ai-gateway/fallbacks). |

## Cache

| Route | Purpose |
|---|---|
| `DELETE /admin/cache` | Clear cached entries. Query: `callType`, `namespace` (a virtual key id) — omit both to clear everything. See [Caching](https://boelabs.com/docs/ai-gateway/caching). |

## Logs and usage

| Route | Purpose |
|---|---|
| `GET /admin/logs` | Paginated operation summaries. Filters include `outcome`, `degraded`, `active`, `terminalVerified`, `failureKind`, `failurePhase`, duration, model, key, request id, cache, and time range. |
| `GET /admin/logs/:operationId` | Full operation record, ordered upstream-attempt timeline, and whether a retained sample exists and can be read (`payload.retained`, `payload.readable`, `payload.access`). |
| `GET /admin/logs/:operationId/payload` | Decrypt the retained forensic sample kept for every finished operation. Requires `payloads:read` (master, owner, or admin) and is audit logged, outcome included. 403 `payload_access_sealed` when the deployment runs `OBSERVABILITY_PAYLOAD_ACCESS=sealed`, 404 once retention has swept the sample, 409 when it outlived its key. |
| `GET /admin/observability/summary` | Outcomes, degraded/stalled/active/abandoned counts, retries, and latency percentiles. Takes a trailing `window=5m|1h|24h` or an explicit `start`/`end` range of at most 31 days; `active` always counts what is in flight now. |
| `GET /admin/usage` | Consumer usage/cost and estimated upstream cost across every attempt; group by `public_model`, `virtual_key`, `actor`, `hour`, `day`, or `none`. Filterable by `actor`. See [Cost accounting](https://boelabs.com/docs/ai-gateway/cost-accounting). |

## Audit trail

| Route | Purpose |
|---|---|
| `GET /admin/audit` | Paginated, newest first. Merges the record of mutating `/admin` calls with the record of retained-payload reads. Query: `kind` (`admin`, `payload_access`), `actor`, `action`, `targetType`, `start`, `end`. Requires `audit:read` (master key or owner). |

Both tables are read and interleaved, so `offset + limit` is capped at 5000 entries: older ones are
reached with a date range or a narrower filter rather than a deeper page. Configuration entries are
kept for `ADMIN_AUDIT_RETENTION_DAYS` (365 by default) and payload-read entries for as long as the
operation metadata they point at.

Reads are not audited — they are the overwhelming majority of dashboard traffic and would bury the
entries that matter. The deliberate exceptions are the reads that reveal a secret:
`GET /admin/logs/:id/payload` reveals real prompts and completions, so it lands here as a
`payload_access` entry, and `GET /admin/keys/:id/secret` reveals a key, so every copy — refused or
not — lands here as a configuration entry. The trail is
append-only; there is no write or delete route for it.

## Idempotent creates

Send `Idempotency-Key: <opaque string>` with a `POST` and the gateway remembers what that key
produced. Retrying with the same key returns the original response — same status, same body, plus
`Idempotent-Replay: true` — instead of creating a second resource. It is opt-in: a request without
the header behaves exactly as it always has.

- Keys are scoped to the caller and expire after 24 hours.
- Reusing a key with a different body is `409 idempotency_key_reuse`, because that is a client bug
  rather than a retry.
- A retry that arrives while the first request is still running is `409 idempotency_key_in_progress`.
- Only successful responses are remembered. A rejected or failed call releases the key, so the
  corrected request can reuse it.
- The remembered response is encrypted with the keyring, because some carry a secret (a new virtual
  key's plaintext). If the key that encrypted it has been retired, a retry gets
  `409 idempotency_response_unreadable`: check whether the first call took effect before retrying
  with a new key.

## Operators and sessions

Present only when `DASH_ENABLED`. `/auth/*` is deliberately mounted outside `/admin`: every `/admin`
route requires an already-resolved operator identity, so the route that creates one cannot live under
it.

| Route | Purpose |
|---|---|
| `GET /auth/config` | Unauthenticated probe: whether human authentication exists here. Always present. |
| `POST /auth/session` | Log in. Sets the httpOnly session cookie and the readable CSRF cookie. Rate limited per username and per IP. |
| `GET /auth/session` | Current identity and the permissions its role grants. |
| `DELETE /auth/session` | Log out; revokes the session server-side and clears both cookies. |
| `POST /auth/password` | Change your own password. Revokes your other sessions and reissues the current one. Not available to the root operator. |
| `GET /admin/users` | List operators. Query: `limit`, `offset`, `role`, `q`. Owner only. |
| `POST /admin/users` | Create an operator. The only way an account comes into existence. Owner only. |
| `GET /admin/users/:id` | Read one operator. Owner only. |
| `PATCH /admin/users/:id` | Change role or enabled state. Either one revokes that user's live sessions. Owner only. |
| `POST /admin/users/:id/password` | Set a user's password and revoke their sessions. Owner only. |
| `DELETE /admin/users/:id` | Delete an operator and their sessions. Owner only. |
| `GET /admin/users/:id/sessions` | That user's live sessions. Never includes the stored token hash. Owner only. |

There is no per-session revoke: disabling the account, changing its role, or setting a new password
revokes every session it has.

The root operator (`DASH_ROOT_USER`) never appears in `GET /admin/users` and cannot be edited through
the API: it is an environment identity, not a row. See
[Security → Dashboard authentication](https://boelabs.com/docs/ai-gateway/security#dashboard-authentication).

## Extensions

| Route | Purpose |
|---|---|
| `GET /admin/extensions` | Loaded definitions and configured instances, with live status. |
| `GET /admin/extensions/artifacts` | List uploaded extension modules (by key). |
| `GET /admin/extensions/artifacts/:key/versions` | Version history for one module key. |
| `POST /admin/extensions/artifacts` | Upload a new version of a module (`key`, `code`). Triggers a hot-reload on every replica. |
| `POST /admin/extensions/artifacts/:key/activate` | Roll back/forward to a specific previously-uploaded version. |
| `DELETE /admin/extensions/artifacts/:key` | Delete a module and all its versions. |
| `GET /admin/extensions/instances` | List configured instances (definition bindings). |
| `POST /admin/extensions/instances` | Create an instance (`id`, `definition`, plus `enabled`/`critical`/`priority`/`match`/`config`). |
| `PATCH /admin/extensions/instances/:id` | Update an instance. |
| `DELETE /admin/extensions/instances/:id` | Remove an instance. |
| `POST /admin/extensions/:id/reset` | Clear a tripped circuit breaker and re-activate the instance for the current process. |

Full walkthrough, hook semantics, and failure behavior: [Runtime extensions](https://boelabs.com/docs/ai-gateway/extensions).

## Next steps

- [API overview](https://boelabs.com/docs/ai-gateway/api-overview) — auth and error shape shared with `/v1/*`.
- [Creating deployments](https://boelabs.com/docs/ai-gateway/creating-deployments), [Virtual keys](https://boelabs.com/docs/ai-gateway/virtual-keys), [Fallbacks](https://boelabs.com/docs/ai-gateway/fallbacks), [Extensions](https://boelabs.com/docs/ai-gateway/extensions) — the guides behind each section above.

