AI AdminPanel Documentation

Authentication API

Use Keycloak OIDC for browser sign-in and panel API keys for automation. These are panel interfaces, separate from Partner Center's OIDC login and pcp_ tokens. The router also retains legacy password/JWT endpoints; do not mistake POST /api/v1/auth/login for the OIDC browser entry point.

OIDC Login Flow

The browser opens GET /api/v1/auth/oidc/login, which redirects to Keycloak using Authorization Code with PKCE. Keycloak returns to GET /api/v1/auth/oidc/callback; the panel exchanges the code, creates a session, sets oidc_session, and redirects to its configured frontend callback. The login handler does not implement a caller-selected redirect query parameter.

EndpointPurpose
GET /api/v1/auth/oidc-configFrontend OIDC configuration
GET /api/v1/auth/oidc/loginBrowser sign-in redirect; 503 if OIDC is unconfigured
GET /api/v1/auth/oidc/callbackProvider callback, not a direct integration call
POST /api/v1/auth/oidc/logoutClears the cookie, attempts session-row deletion, returns redirect_url for the browser to complete Keycloak logout
GET /api/v1/auth/meAuthenticated identity, wrapped in user

Example identity response (synthetic UUID and email):

{
  "user": {
    "id": "10000000-0000-4000-8000-000000000001",
    "email": "[email protected]",
    "role": "admin"
  }
}

Logout's JSON response alone does not complete the browser's Keycloak logout; the browser follows redirect_url. Do not assume it revokes every other session.

API Keys

Create a key in Settings → Security → API Keys, or call POST /api/v1/api-keys using an already authenticated session or authorized key. The full secret is returned once. Keep it in a secrets manager and revoke it if lost or exposed.

Creating an API Key

curl -X POST https://panel.example.com/api/v1/api-keys \
  -H "Authorization: Bearer ${PANEL_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{"name":"inventory","role":"admin","rateLimitRpm":60}'
FieldContract
nameRequired
roleadmin or customer; defaults to the caller's role. A customer cannot mint an admin key.
scopesOptional string array; specialized tenant MCP keys use the MCP surface below.
permissionsDenyOptional array of known permission names
expiresAtOptional future RFC3339 timestamp; omitted means no expiry
rateLimitRpmPositive requests/minute; defaults to 60
isServiceAccount, descriptionOptional metadata

201 Created returns key and metadata, rather than a flat key-record object:

{
  "key": "REDACTED_PANEL_API_KEY",
  "metadata": {
    "name": "inventory",
    "scopes": null,
    "role": "admin",
    "permissionsDeny": null,
    "rateLimitRpm": 60,
    "expiresAt": null,
    "isServiceAccount": false,
    "description": ""
  }
}

The redacted value above is not usable. Customer callers also go through the api_keys capability, organization attachment, and key-count policy; successful customer minting includes orgId in metadata. Admin-created keys remain platform-scoped on this route, including admin-created customer-role keys. A role label alone does not select a tenant.

A customer caller must hold, in its organization, every permission of the key's role; otherwise the response is 403 with the code insufficient_role and no key is created. A key bound to an organization works only for that organization and the ones below it, with the key's own role, cut down to what its creator holds there now, and answers 404 for everything else. Called with such a key, POST /api-keys creates keys only for the same organization, and GET /api-keys and DELETE /api-keys/{id} see only the creator's keys bound to it. One exception until the next release: /secrets* and /portal/* take the customer from the key's creator, not from the key, so there a key still follows its creator (AI-1055, slice 2). See API Keys.

Using an API Key

curl https://panel.example.com/api/v1/services \
  -H "Authorization: Bearer ${PANEL_API_KEY}"

Choose the role and endpoint for the intended account. Admin service/template routes require admin; customer integrations use the portal API. Permission-deny settings affect routes that enforce those permissions and should not be treated as a universal read-only replacement for an admin key.

Listing API Keys

GET /api/v1/api-keys returns {"keys": [...]} for the authenticated user. Each record includes its UUID id, name, prefix, role, scopes, policy metadata, and timestamps; the full key is not returned. Use that UUID when revoking.

Revoking an API Key

DELETE /api/v1/api-keys/{id} revokes a key owned by the authenticated user and returns 204 No Content. Subsequent authentication with the revoked key fails.

Session Management

Security limitation in v2.12.8: The OIDC session implementation stores access and refresh tokens using repeating-key XOR, without authenticated encryption. Do not rely on this storage format to protect tokens if the session database is exposed. Protect database and backup access; this limitation needs separate product-security remediation.

Session Storage

OIDC sessions are stored in PostgreSQL's identity_sessions table. The browser receives an opaque session identifier in the oidc_session cookie. Its attributes are:

  • HttpOnly — not accessible via JavaScript
  • Secure — only sent over HTTPS
  • SameSite=Lax — limits when browsers send the cookie across sites
  • Path=/; when configured, the cookie domain also covers service subdomains

Session Expiry

The login callback sets the cookie lifetime and server-side session expiry from Keycloak's token response (expires_in). The request middleware checks that stored expiry; it does not extend it on each request. Sign in again when the session expires.

Role-Based Access

The main services and templates routes in this reference belong to the admin-only router group. Customer access is served by /api/v1/portal routes with customer/organization checks. There is no operator or viewer grant for these admin routes. Do not infer tenant isolation from an optional customerId field on an admin request: that field assigns a resource's customer.

MCP Server describes the separate admin and tenant MCP endpoints and the credentials each accepts.