Skip to main content

Administration

Backend API​

Administration spans the auth router in llm_d_lens/auth/router.py and the system-settings router in llm_d_lens/db/system_router.py.

Auth and administration routes live under the /api/v1 prefix from llm_d_lens/auth/router.py.

Resource binding restrictions​

Model inference grants replace deployment sharing. Resource-scope bindings only accept scopeResourceType: "model_service_group" and the built-in end-user roleId. The model ID and cluster must match stored records, and the recipient must independently hold inference permission in that cluster. Global/cluster role assignments are unchanged.

These restrictions apply to POST /api/v1/users/{user_id}/role-bindings and POST /api/v1/groups/{group_id}/role-bindings (both require role binding management). Successful creation returns HTTP 201 with the binding.

{
"roleId": "existing-built-in-end-user-role-id",
"scopeType": "resource",
"scopeClusterId": "cluster-id",
"scopeResourceType": "model_service_group",
"scopeResourceId": "msg-existing-model-id"
}

Retired deployment resource types, privileged grant roles and cluster mismatches are rejected. Use the scoped model access API for routine model grants; it derives role and cluster rather than accepting them.

List users​

List users, including direct roles, group memberships, and inherited roles.

GET /api/v1/users

Request:

  • Query params:
    • query (string, optional): filter by username or other user-search text.
    • status (string, optional): filter by user status such as active or disabled.

This endpoint has no request body.

Response:

Returns an array of user records enriched with direct roles, groups, and inheritedRoles.

[
{
"username": "alice",
"display_name": "Alice Admin",
"id": "user-1",
"email": "alice@example.com",
"provider_id": null,
"auth_source": "local",
"external_id": null,
"status": "active",
"failed_login_count": 0,
"locked_until": null,
"must_change_password": false,
"password_changed_at": "2026-09-21T08:00:00+00:00",
"last_login_at": "2026-09-23T08:45:00+00:00",
"principal_version": 3,
"created_at": "2026-09-20T10:00:00+00:00",
"updated_at": "2026-09-23T08:45:00+00:00",
"has_password": true,
"protected": false,
"roles": [
{
"name": "maintainer",
"scopeType": "cluster",
"scopeClusterId": "cluster-123"
}
],
"groups": [
{
"id": "group-ops",
"name": "ops",
"source": "manual"
}
],
"inheritedRoles": [
{
"name": "end-user",
"scopeType": "global",
"scopeClusterId": null,
"scopeResourceType": null,
"scopeResourceId": null,
"groupId": "group-ops",
"groupName": "ops"
}
]
}
]

Create a user​

Create a user account.

POST /api/v1/users

Request:

  • JSON body fields:
    • username (string, required)
    • password (string, optional)
    • displayName (string, optional)
    • email (string, optional)
{
"username": "alice",
"password": "<strong-password>",
"displayName": "Alice Admin",
"email": "alice@example.com"
}

Response:

Returns the created user record from _user_public(...).

{
"username": "alice",
"display_name": "Alice Admin",
"id": "user-1",
"email": "alice@example.com",
"provider_id": null,
"auth_source": "local",
"external_id": null,
"status": "active",
"failed_login_count": 0,
"locked_until": null,
"must_change_password": false,
"password_changed_at": null,
"last_login_at": null,
"principal_version": 1,
"created_at": "2026-09-23T09:00:00+00:00",
"updated_at": "2026-09-23T09:00:00+00:00",
"has_password": true,
"protected": false
}

Update a user​

Update a user's display name, email, or status.

PATCH /api/v1/users/{user_id}

Request:

  • Path params:
    • user_id (string, required): the user record to update.
  • JSON body fields:
    • displayName (string, optional)
    • email (string, optional)
    • status (string, optional)
{
"displayName": "Alice Operations",
"email": "alice.ops@example.com",
"status": "active"
}

Response:

Returns the updated user record.

{
"username": "alice",
"display_name": "Alice Operations",
"id": "user-1",
"email": "alice.ops@example.com",
"provider_id": null,
"auth_source": "local",
"external_id": null,
"status": "active",
"failed_login_count": 0,
"locked_until": null,
"must_change_password": false,
"password_changed_at": null,
"last_login_at": "2026-09-23T08:45:00+00:00",
"principal_version": 2,
"created_at": "2026-09-20T10:00:00+00:00",
"updated_at": "2026-09-23T09:05:00+00:00",
"has_password": true,
"protected": false
}

List roles​

List roles.

GET /api/v1/roles

Request:

This endpoint has no request body.

Response:

Returns an array of role records with their resolved permission codes.

[
{
"name": "maintainer",
"description": "Manage clusters and workloads",
"id": "role-maintainer",
"is_builtin": true,
"created_at": "2026-09-20T09:00:00+00:00",
"updated_at": "2026-09-20T09:00:00+00:00",
"permissions": [
"cluster:cluster:read",
"deployment:run:read",
"session:session:revoke"
]
}
]

Create a role​

Create a role.

POST /api/v1/roles

Request:

  • JSON body fields:
    • name (string, required)
    • description (string, optional)
    • permissions (array<string>, optional)
{
"name": "auditor",
"description": "Read-only compliance role",
"permissions": [
"deployment:run:read",
"cluster:cluster:read"
]
}

Response:

Returns the newly created role and its permission set.

{
"name": "auditor",
"description": "Read-only compliance role",
"id": "role-auditor",
"is_builtin": false,
"created_at": "2026-09-23T09:10:00+00:00",
"updated_at": "2026-09-23T09:10:00+00:00",
"permissions": [
"cluster:cluster:read",
"deployment:run:read"
]
}

Set role permissions​

Set a custom role's permissions.

PUT /api/v1/roles/{role_id}/permissions

Request:

  • Path params:
    • role_id (string, required): the custom role to update.
  • JSON body fields:
    • permissions (array<string>, required)
{
"permissions": [
"cluster:cluster:read",
"deployment:run:read",
"session:session:revoke"
]
}

Response:

Returns 204 No Content when the permission set is stored successfully. This endpoint does not return a JSON body.

List sessions​

List sessions; admins can include revoked sessions, while other callers see their own sessions.

GET /api/v1/sessions

Request:

  • Query params:
    • include_revoked (boolean, optional): admins can include revoked sessions in the result set.

This endpoint has no request body.

Response:

Returns a list of session records. Admin callers receive every session plus a username field; non-admin callers receive only their own sessions.

[
{
"user_id": "user-1",
"token_hash": "$argon2id$v=19$m=65536,t=3,p=4$sessionhash",
"id": "session-1",
"created_at": "2026-09-23T08:45:00+00:00",
"expires_at": "2026-09-23T16:45:00+00:00",
"idle_expires_at": "2026-09-23T10:45:00+00:00",
"last_seen_at": "2026-09-23T09:15:00+00:00",
"revoked_at": null,
"auth_source": "local",
"ip": "127.0.0.1",
"user_agent": "Mozilla/5.0",
"username": "alice"
}
]

Revoke a session​

Revoke a session.

DELETE /api/v1/sessions/{session_id}

Request:

  • Path params:
    • session_id (string, required): the session record to revoke.

This endpoint has no request body.

Response:

Returns 204 No Content after the target session is revoked. This endpoint does not return a JSON body.

List cluster access bindings​

List cluster-scoped access bindings for a cluster.

GET /api/v1/clusters/{cluster_id}/access

Request:

  • Path params:
    • cluster_id (string, required): the cluster whose user and group bindings should be listed.

This endpoint has no request body.

Response:

Returns a list of user and group bindings, each tagged with subjectType.

[
{
"subjectType": "user",
"user_id": "user-1",
"role_id": "role-maintainer",
"id": "binding-user-1",
"scope_type": "cluster",
"scope_cluster_id": "cluster-123",
"scope_resource_type": null,
"scope_resource_id": null,
"granted_by_user_id": "admin-1",
"expires_at": null,
"created_at": "2026-09-23T09:20:00+00:00"
},
{
"subjectType": "group",
"group_id": "group-ops",
"role_id": "role-end-user",
"id": "binding-group-1",
"scope_type": "cluster",
"scope_cluster_id": "cluster-123",
"scope_resource_type": null,
"scope_resource_id": null,
"granted_by_user_id": "admin-1",
"expires_at": null,
"created_at": "2026-09-23T09:21:00+00:00"
}
]

Grant cluster access​

Grant cluster-scoped access to a user or group.

POST /api/v1/clusters/{cluster_id}/access

Request:

  • Path params:
    • cluster_id (string, required): the authoritative cluster scope for the binding.
  • JSON body fields:
    • subjectType ("user" | "group", required)
    • subjectId (string, required)
    • roleId (string, required)
    • clusterId (string, optional): present in the request model, but the route uses the {cluster_id} path parameter as the scope.
{
"subjectType": "user",
"subjectId": "user-1",
"roleId": "role-maintainer"
}

Response:

Returns the created binding record.

{
"user_id": "user-1",
"role_id": "role-maintainer",
"id": "binding-user-1",
"scope_type": "cluster",
"scope_cluster_id": "cluster-123",
"scope_resource_type": null,
"scope_resource_id": null,
"granted_by_user_id": "admin-1",
"expires_at": null,
"created_at": "2026-09-23T09:20:00+00:00"
}

Revoke cluster access​

Revoke a cluster-scoped access binding.

DELETE /api/v1/clusters/{cluster_id}/access/{binding_id}

Request:

  • Path params:
    • cluster_id (string, required): the cluster whose binding is being removed.
    • binding_id (string, required): the user or group binding id to revoke.

This endpoint has no request body.

Response:

Returns 204 No Content after the binding is removed. This endpoint does not return a JSON body.

The database setup routes below live under the /api/v1/system path from llm_d_lens/db/system_router.py.

Read database setup status​

Read whether Lens' own database has already been configured.

GET /api/v1/system/database

Request:

This endpoint has no request body.

Response:

Returns a DatabaseStatus object. displayTarget is masked host/port/database information only; it never echoes credentials.

{
"configured": true,
"mode": "external",
"engine": "postgresql",
"displayTarget": "db.example.com:5432/prism"
}

Configure the backend database​

Configure Lens' own database connection (external or embedded) and create the initial tables.

POST /api/v1/system/database

Request:

  • JSON body fields:
    • mode ("external" | "embedded", required)
    • engine ("postgresql" | "mysql" | "oracle" | "mssql", optional)
    • databaseUrl (string, required for external mode)
    • embeddedPassword (string, optional; only meaningful for embedded mode)

External-database example:

{
"mode": "external",
"engine": "mysql",
"databaseUrl": "mysql://prism:secret@db.example.com:3306/prism"
}

Response:

Returns the resulting DatabaseStatus after connection probing, engine configuration, and initial table creation succeed.

{
"configured": true,
"mode": "external",
"engine": "mysql",
"displayTarget": "db.example.com:3306/prism"
}

The master-key routes below also live at an /api/v1/system/... path, but they are registered by llm_d_lens/auth/router.py (not system_router.py), alongside the other auth and administration endpoints.

Read the stored-secret master key status​

Read the effective master key (LENS_SECRET_KEY) source, a non-reversible fingerprint, and how many provider secrets and old fallback keys exist. The key value itself is never returned.

GET /api/v1/system/secret-key

Request:

This endpoint has no request body.

Response:

{
"source": "file",
"envLocked": false,
"configured": true,
"fingerprint": "3f9a1c7b52e0d4a6",
"keyLength": 64,
"oldKeyCount": 1,
"path": "/home/user/.local/share/lens/credentials/master_key.json",
"providerSecretCount": 2
}

source is environment, file, or unconfigured; envLocked is true when LENS_SECRET_KEY is supplied through the environment (the stored key is then ignored and rotation through the API is rejected).

Rotate the stored-secret master key​

Generate a new master key (or use a supplied one), re-encrypt every stored provider secret with it, and retain the previous key(s) as decryption fallback. Rejected when LENS_SECRET_KEY is environment-locked.

POST /api/v1/system/secret-key/rotate

Request:

  • JSON body fields:
    • newKey (string, optional; leave empty to auto-generate a strong key)
{
"newKey": "a-manually-chosen-strong-key"
}

Response:

Returns the new status plus rotatedSecrets, the number of provider secrets re-encrypted.

{
"source": "file",
"envLocked": false,
"configured": true,
"fingerprint": "b71e0c9d3a4f8215",
"keyLength": 28,
"oldKeyCount": 1,
"path": "/home/user/.local/share/lens/credentials/master_key.json",
"providerSecretCount": 2,
"rotatedSecrets": 2
}

Clear previous master keys​

Drop the retained fallback keys once every provider secret has been re-encrypted. Secrets still encrypted with an old key become unreadable, so re-save those providers first.

DELETE /api/v1/system/secret-key/old

Request:

This endpoint has no request body.

Response:

Returns the status with oldKeyCount set to 0.

Additional auth endpoints also exist for login/session state, groups, identity providers, audit logs, password changes, and bootstrap; see the OpenAPI reference.

See the full request/response schemas in the FastAPI OpenAPI schema (regenerate with npm run docs:api).