OpenClaw EnterpriseDOCSGitHub

Service accounts

A service account is an OCC-owned, Namespace-scoped identity that links Agents to one credential without exposing its value. It is neither a platform ServicePrincipal, a Kubernetes ServiceAccount, nor a provider account. Native accounts accept existing API-key references; an optionally selected ServiceAccountDriver can instead create and manage an upstream account while keeping its provider-specific identity private. A managed account's private binding records its exact Backend, Driver, and workspace ownership.

This page defines current account behavior and credential boundaries. For controller setup and authentication, see the quickstart and deployment guide.

Account ownership and authorization

The server assigns each account an sa_-prefixed ID inside exactly one Namespace. Multiple Agents in that Namespace may share the account. Each account has at most one credential reference; its provider identity and credential bytes remain private. Creating an account is supported while its Namespace is provisioning or ready.

Operation Required exact permission
POST /namespaces/:namespaceId/service-accounts create on the Namespace's account collection.
GET /namespaces/:namespaceId/service-accounts read on the Namespace and each returned account.
GET /namespaces/:namespaceId/service-accounts/:serviceAccountId read on the account.
POST /namespaces/:namespaceId/service-accounts/:serviceAccountId/credentials update on the exact account.
PATCH /namespaces/:namespaceId/service-accounts/:serviceAccountId/credential update on the native account.
DELETE /namespaces/:namespaceId/service-accounts/:serviceAccountId delete on the unreferenced account.

Account creation and credential issuance are separate. OCC authorizes each operation before provider or Kubernetes effects; provider authorization remains independent. Responses expose only OCC account metadata and an optional generic credential readiness metadata, never backend locators, provider identities, or credential bytes. Collection reads require read on the Namespace and return only accounts for which the caller also has exact-account read.

Backend selection and configuration

The optional ChatGPT implementation requires an Installation-scoped Backend and its matching selected service_account Driver. The Backend reference owns the complete YAML, client and membership contract, mounted key, and Helm values. The configuration requires durable PostgreSQL persistence.

Both processes load nonsecret Backend definitions; only the API initializes Backend<ChatGPTClient> and injects it into ChatGPTServiceAccountDriver. The worker validates metadata without an admin credential or Backend client.

Account and credential lifecycle

Creation and credential issuance are separate authorized operations. With the ServiceAccount Driver selected, POST /namespaces/:namespaceId/service-accounts accepts a name, returns 201 with an OCC account envelope, and privately links the newly created upstream account. Without that Driver, creation produces a native OCC account. Neither operation issues a credential automatically.

A representative account-creation body is:

json
{ "name": "support-model" }

POST /namespaces/:namespaceId/service-accounts/:serviceAccountId/credentials accepts {} and issues a credential through the selected Driver. The 201 account envelope exposes safe credential readiness metadata; backend Secret locators and Backend/workspace identities remain private. Compute creates one account-owned token Secret in the tenant control plane; the Driver privately persists the upstream credential ID for exact cleanup. A second issuance fails with 409; rotation and reconciliation are not implemented. On an Installation with no ChatGPT Backend, and so no ServiceAccount Driver, issuance fails with 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED naming the fix, after the account update check and lookup. A selected Driver that is unavailable or fails returns 503 DEPENDENCY_UNAVAILABLE.

An Agent binds the same-Namespace account through harnessAuth: { method: "codex_pat", source: { kind: "service_account", namespaceId, id: serviceAccountId } }. Association and deployment require read on the exact account. Updating or detaching an associated account requires current-account read; replacement requires read on both accounts. An Agent can reference an account before it has a credential, but deployment rejects that state.

Deletion requires delete on the exact account and is rejected with 409 while an Agent draft, selected version, queued or claimed deployment, or queued or running Agent provisioning request references it; the message lists these kinds, not the specific resources. Detaching the draft alone does not release an active or pending deployment's account. Inactive historical versions and permanently failed deployments do not block deletion unless the account is still referenced by other live state. Backend-managed deletion removes the exact upstream credential, the account-owned Secret, and the upstream account before deleting OCC account state. If the account holds an issued access token and the Installation no longer has a ChatGPT Backend, deletion fails with 409 SERVICE_ACCOUNT_DRIVER_NOT_CONFIGURED, after the delete grant and the account lookup, and keeps the account: configure the same ChatGPT Backend again (same backendId), then retry. Native deletion removes OCC account state; the operator owns the referenced source Secret.

Force-delete when the Backend is gone

If that Backend is retired for good and the Installation has no ChatGPT Backend, force the delete with the same delete grant: DELETE .../service-accounts/:serviceAccountId?force=true, or occ service-account delete ID --force. OCC removes the account, its access-token Secret, and its private Backend binding, and answers 200 with revocation: "skipped". The token and the provider account (named <name>-<serviceAccountId>, credential occ-<serviceAccountId>) stay at the provider until an administrator deletes them there or the token expires. The audit event records force: true, revocation: "skipped", the actor, and the binding's backendId, workspaceId, externalAccountId, and provider credentialId, never the token.

Force changes nothing else. With a ChatGPT Backend configured it is ignored (the audit event still records force: true): the token is revoked and the answer is the usual 204, so retried scripts stay safe. If a different ChatGPT Backend replaced the old one, deletion still answers 503 because the binding names another Backend; configure the old Backend again, or remove the ChatGPT Backend, before deleting. Accounts without an issued token, and referenced accounts, behave as without force. Callers without delete get the same 403 or 404. The choice is made on the locked account row, so a token issued meanwhile is reported, not missed. An API Pod still running without the Backend during a rollout that re-adds it can force-delete; avoid forcing until the rollout ends.

Version snapshots and credential delivery

Deploying an Agent freezes the account ID, credential kind, Secret reference, and nullable backendId in its AgentRevision. It does not copy credential bytes into the version. Later account edits do not rewrite that snapshot. A Secret reference is not a snapshot of the Secret's value. Before dispatch, the worker reauthorizes exact-account read for the actor who requested the deployment.

Backend-managed access tokens

For an access_token, the Agent must select the binding's exact nonnull backendId, with its selected member Driver, workspace, and issued credential. Admission and worker reconciliation validate that private metadata before workload effects; a public credential kind is not proof of ownership. Only dedicated Codex execution is supported. Kubernetes delivers the token from the account-owned CP source through a data-plane runtime Secret for that Agent revision into the exact Codex Pod:

Account Secret key Codex environment variable Purpose
token CODEX_ACCESS_TOKEN One upstream account access token.

Both managed-account and Secret sources use codex_pat with CODEX_LOGIN_MODE=codex_pat. Codex authenticates through codex -c cli_auth_credentials_store=file login --with-access-token, derives account identity from the token, and saves login state only in its temporary workload volume, which has a 1Gi size limit. Workspace ownership remains a control-plane admission and reconciliation check; no workspace override is projected into Codex. The separate Gateway receives no model credential. The trusted worker reads the source and manages the version projection; workloads receive no Secret API permission. No API-key fallback is used. The projection does not narrow provider-side token authority.

Native API-key references

PATCH /namespaces/:namespaceId/service-accounts/:serviceAccountId/credential sets or replaces a native credential reference. It cannot set an access_token or manually replace a provider-issued access token. The native API-key body names an existing Secret and key in the account's exact backing namespace:

json
{  "kind": "api_key",  "secretRef": { "name": "provider-key", "key": "api-key" }}

Native account references are not model-auth selectors. To supply an OpenAI API key to either supported Harness, store it as an OCC Secret and select harnessAuth.method: "api_key"; see Agent harness authentication. The Compute-owned account Secret path applies only to Driver-issued access tokens.

oauth_access_token references remain representable, but deployment and refresh are unsupported. OAuth refresh belongs to a future credential-owning provider, not IAM, Compute, OCC, or the Harness.

Failures and current limitations

Every 503 DEPENDENCY_UNAVAILABLE above answers the generic "A required platform dependency is unavailable." The specific cause, such as "could not be removed" or "outcome is unknown", is only in the API's own log: a WARN http.dependency_unavailable record with the response's meta.requestId, the route, the error class and message, and its causes' class and code. The logging Collector exports only the event and request ID.

The controller domain operations own account admission, association, deletion, and version snapshots. The ChatGPT Driver owns private upstream bindings and compensation; the worker reauthorizes the deployment actor.

Search documentation