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.
- REST API
- Python API
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.
from llm_d_lens.auth.service import default_service
def add_inference_binding(user_id: str, model_id: str, cluster_id: str):
service = default_service()
role = service.require_role("end-user")
return service.add_user_binding(
user_id, role.id, scope_type="resource",
scope_cluster_id=cluster_id,
scope_resource_type="model_service_group",
scope_resource_id=model_id,
)
This low-level service is for already-authorized administrator code.
add_group_binding enforces the same constraints for groups. Prefer
ModelService.grant_access(..., principal=principal) to enforce the caller's
model-specific grant-management scope as well.
List users
List users, including direct roles, group memberships, and inherited roles.
- REST API
- Python API
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 asactiveordisabled.
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"
}
]
}
]
from llm_d_lens.auth.service import default_service
service = default_service()
role_names = {role.id: role.name for role in service.role_dao.list()}
group_names = {group.id: group.name for group in service.group_dao.list()}
user_bindings = service.user_binding_dao.list_all()
group_bindings = service.group_binding_dao.list_all()
memberships = service.user_group_dao.list_all()
users = service.user_dao.list(query="alice", status="active")
print(len(users), len(user_bindings), len(group_bindings), len(memberships))
print(role_names)
print(group_names)
Create a user
Create a user account.
- REST API
- Python API
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
}
from llm_d_lens.auth.service import default_service
user = default_service().create_user(
username="alice",
password="<strong-password>",
display_name="Alice Admin",
email="alice@example.com",
)
print(user.id, user.username, user.status)
Update a user
Update a user's display name, email, or status.
- REST API
- Python API
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
}
from llm_d_lens.auth.router import UserUpdateRequest
from llm_d_lens.auth.service import default_service
body = UserUpdateRequest(displayName="Alice Operations", email="alice.ops@example.com", status="active")
updates = body.model_dump(exclude_none=True)
user = default_service().update_user("user-1", **updates)
print(user.display_name, user.email, user.status)
List roles
List roles.
- REST API
- Python API
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"
]
}
]
from llm_d_lens.auth.service import default_service
roles = default_service().role_dao.list()
print([(role.id, role.name) for role in roles])
Create a role
Create a role.
- REST API
- Python API
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"
]
}
from llm_d_lens.auth.service import default_service
role = default_service().create_role(
name="auditor",
description="Read-only compliance role",
permissions={"deployment:run:read", "cluster:cluster:read"},
)
print(role.id, role.name)
Set role permissions
Set a custom role's permissions.
- REST API
- Python API
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.
from llm_d_lens.auth.service import default_service
default_service().set_role_permissions(
"role-auditor",
{"cluster:cluster:read", "deployment:run:read", "session:session:revoke"},
)
print("permissions updated")
List sessions
List sessions; admins can include revoked sessions, while other callers see their own sessions.
- REST API
- Python API
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"
}
]
from llm_d_lens.auth.service import default_service
service = default_service()
admin_view = service.session_dao.list_all(include_revoked=True)
self_view = service.list_sessions("user-1")
print(len(admin_view), len(self_view))
Revoke a session
Revoke a session.
- REST API
- Python API
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.
from llm_d_lens.auth.service import default_service
service = default_service()
service.revoke_session("session-1", allow_any=True)
print("session revoked")
List cluster access bindings
List cluster-scoped access bindings for a cluster.
- REST API
- Python API
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"
}
]
from llm_d_lens.auth.router import _list_access
from llm_d_lens.auth.service import default_service
items = _list_access("cluster-123", None, default_service())
print(items)
Grant cluster access
Grant cluster-scoped access to a user or group.
- REST API
- Python API
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"
}
from llm_d_lens.auth.service import default_service
binding = default_service().add_user_binding(
"user-1",
"role-maintainer",
scope_type="cluster",
scope_cluster_id="cluster-123",
granted_by="admin-1",
)
print(binding.id, binding.scope_cluster_id)
Revoke cluster access
Revoke a cluster-scoped access binding.
- REST API
- Python API
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.
from llm_d_lens.auth.service import default_service
service = default_service()
for binding in service.user_binding_dao.list_all():
if binding.id == "binding-user-1":
service.remove_user_binding(binding.user_id, binding.id)
break
else:
for binding in service.group_binding_dao.list_all():
if binding.id == "binding-group-1":
service.remove_group_binding(binding.group_id, binding.id)
break
print("binding revoked")
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.
- REST API
- Python API
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"
}
from llm_d_lens.db.system_router import get_database_status
status = get_database_status()
print(status.configured, status.mode, status.display_target)
Configure the backend database
Configure Lens' own database connection (external or embedded) and create the initial tables.
- REST API
- Python API
POST /api/v1/system/database
Request:
- JSON body fields:
mode("external" | "embedded", required)engine("postgresql" | "mysql" | "oracle" | "mssql", optional)databaseUrl(string, required forexternalmode)embeddedPassword(string, optional; only meaningful forembeddedmode)
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"
}
from llm_d_lens.db.system_router import DatabaseSetupRequest, apply_database_setup
status = apply_database_setup(
DatabaseSetupRequest(
mode="external",
engine="mysql",
databaseUrl="mysql://prism:secret@db.example.com:3306/prism",
)
)
print(status.configured, status.mode, status.engine)
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.
- REST API
- Python API
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).
from llm_d_lens.auth.master_key import status
from llm_d_lens.auth.settings import get_settings
print(status(get_settings())["source"], status(get_settings())["fingerprint"])
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.
- REST API
- Python API
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
}
from llm_d_lens.auth.master_key import rotate_master_key
from llm_d_lens.auth.settings import get_settings
from llm_d_lens.db.dao.identity_provider import IdentityProviderDao
result = rotate_master_key(get_settings(), None, provider_dao=IdentityProviderDao())
print(result["fingerprint"], result["rotatedSecrets"])
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.
- REST API
- Python API
DELETE /api/v1/system/secret-key/old
Request:
This endpoint has no request body.
Response:
Returns the status with oldKeyCount set to 0.
from llm_d_lens.auth.master_key import clear_old_keys
from llm_d_lens.auth.settings import get_settings
print(clear_old_keys(get_settings())["oldKeyCount"])
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).