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.
| Endpoint | Purpose |
|---|---|
GET /api/v1/auth/oidc-config | Frontend OIDC configuration |
GET /api/v1/auth/oidc/login | Browser sign-in redirect; 503 if OIDC is unconfigured |
GET /api/v1/auth/oidc/callback | Provider callback, not a direct integration call |
POST /api/v1/auth/oidc/logout | Clears the cookie, attempts session-row deletion, returns redirect_url for the browser to complete Keycloak logout |
GET /api/v1/auth/me | Authenticated identity, wrapped in user |
Example identity response (synthetic UUID and email):
{
"user": {
"id": "10000000-0000-4000-8000-000000000001",
"email": "admin@example.test",
"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}'
| Field | Contract |
|---|---|
name | Required |
role | admin or customer; defaults to the caller's role. A customer cannot mint an admin key. |
scopes | Optional string array; specialized tenant MCP keys use the MCP surface below. |
permissionsDeny | Optional array of known permission names |
expiresAt | Optional future RFC3339 timestamp; omitted means no expiry |
rateLimitRpm | Positive requests/minute; defaults to 60 |
isServiceAccount, description | Optional 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.
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 JavaScriptSecure— only sent over HTTPSSameSite=Lax— limits when browsers send the cookie across sitesPath=/; 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.