KLA Digital Logo
KLA Digital
Getting Started

Authentication

Authenticate against the KLA Control Plane API with OAuth 2.0 sign-in for humans and client-credentials service accounts for machines.

4 min read944 words

Every call to the KLA Control Plane API is authenticated with a short-lived OAuth 2.0 bearer access token and scoped to a single tenant. The KLA Control Plane is a govern-in-place runtime safety, audit, and governance layer for enterprise AI agents; its API is how you register agents, evaluate policies, route approvals, and pull evidence. This page explains the two credential types, how to obtain a token, how to call the API, and how to keep credentials least-privilege and rotated.

Two credential types

KLA issues tokens through its identity provider, an OpenID Connect (OIDC) service. Which flow you use depends on who is calling.

Interactive sign-in Service account
Caller A human in the Console (the KLA web app) A backend service, script, or CI job
Flow OAuth 2.0 / OIDC with PKCE OAuth 2.0 client credentials
Secret None stored by the app client_id + client_secret
Identity A person, with their roles A machine principal you create
Use for Reviewing the Decision Desk, building policies SDK and API calls, automation

Interactive sign-in is what the Console uses. The browser runs the OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange), so no client secret ever lives in front-end code. You do not implement this yourself; it ships with the Console.

Service accounts are what your integrations use. You create a service-account client per integration, give it the minimum roles it needs, and exchange its client_id and client_secret for an access token using the client-credentials grant.

flowchart LR
  H["Human in Console"] -->|"PKCE sign-in"| IDP["KLA identity provider"]
  M["Backend service"] -->|"client credentials"| IDP
  IDP -->|"bearer token"| API["KLA Control Plane API"]

Getting a service-account token

Exchange your client credentials at the identity provider's token endpoint. The realm path is your tenant, so substitute your tenant slug for <tenant>.

curl -s -X POST \
  "https://auth.kla.digital/realms/<tenant>/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=svc-claims-triage" \
  -d "client_secret=$KLA_CLIENT_SECRET"

The response is a JSON object containing the bearer access token and its lifetime in seconds:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "expires_in": 300,
  "token_type": "Bearer",
  "scope": "agents:read decisions:write"
}

Capture the token for the calls that follow:

export KLA_ACCESS_TOKEN=$(curl -s -X POST \
  "https://auth.kla.digital/realms/<tenant>/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=svc-claims-triage" \
  -d "client_secret=$KLA_CLIENT_SECRET" | jq -r .access_token)

Calling the API

Send the token in the Authorization header and name your tenant in the x-tenant-id header. The API host is https://api.kla.digital. Use this round-trip to confirm your token works:

curl -s https://api.kla.digital/v1/tenants.current \
  -H "Authorization: Bearer $KLA_ACCESS_TOKEN" \
  -H "x-tenant-id: <tenant>"

A successful call returns the tenant the token is scoped to:

{
  "tenant": { "id": "acme", "name": "Acme Corp", "region": "eu" }
}
🛡️ Important
The token already encodes a tenant, and `x-tenant-id` must match it. Sending a token for one tenant with another tenant's header is rejected with `403`. This double check keeps every request inside one tenant boundary.

The sequence below shows the full path a machine client follows on each token cycle.

sequenceDiagram
  participant C as Client service
  participant IDP as KLA identity provider
  participant API as KLA Control Plane API
  C->>IDP: POST token endpoint with client credentials
  IDP-->>C: access_token plus expires_in
  C->>API: GET v1/tenants.current with bearer plus x-tenant-id
  API-->>C: 200 tenant payload
  Note over C,IDP: On 401 expired, request a fresh token and retry

Token lifetime and refresh

Access tokens are short-lived, typically five minutes (expires_in: 300). This limits the blast radius of a leaked token. Do not pin or cache a token past its expiry.

  • Service accounts simply request a new token from the token endpoint when the current one nears expiry. The client-credentials grant does not issue a refresh token; re-running the exchange is the refresh.
  • Interactive Console sessions use a separate, longer-lived refresh token managed by the browser to obtain new access tokens silently.
  • A 401 Unauthorized from the API means the token is expired or invalid. Request a fresh token and retry once.

Least-privilege scopes and roles

Each token carries the roles of its principal, and the API authorizes every request against them. Grant a service account only the roles its job requires:

  • An agent that emits telemetry needs write access to traces, not policy authoring.
  • An approvals automation needs decisions:read and decisions:write, not agent deployment rights.
  • A read-only evidence exporter needs evidence read access and nothing else.

Create one service account per integration so you can revoke or re-scope a single caller without disrupting others. Roles are managed in the Agent Registry and tenant settings of the Console.

Rotating service-account secrets

A client_secret is a long-lived credential: treat it like a database password. Rotate it on a schedule and immediately on any suspected exposure.

Store and rotate service-account secrets in the Secrets Vault, the Console surface for sensitive storage. Generate a new secret there, deploy it to your integration, confirm new tokens mint correctly, then retire the old secret. The Secrets Vault supports overlapping secrets during a rotation so you can roll forward with zero downtime.

⚠️ Warning
Never embed a long-lived `client_secret`, or any service-account credential, in browser code, mobile apps, public repositories, or client-side bundles. Anything shipped to a user's device or a public repo is compromised. Keep service-account secrets server-side only, inject them from the Secrets Vault or your secret manager at runtime, and use the Console's PKCE sign-in for anything a human touches.
Authentication | Developer Docs | KLA Control Plane