- Service account management: Create, rotate, delete service accounts and issue JWT tokens. Restricted to privileged workspaces.
- Org API key management: Create, list, rotate, revoke org API keys scoped to the privileged workspace. Restricted to privileged workspaces.
- Product bindings: CRUD operations on the
product_bindingscollection to link resources to users, orgs, or groups. Available to any workspace. - Access checking: High-level
checkAccessfunction that combines permissions, scopes, and bindings to determine if a caller can access a resource. Available to any workspace.
Service Account Functions
getServiceAccountToken: Get a JWT token for a service account
Returns the token response including
accessToken, tokenType, expiresAt, permissions, and scopes.
createServiceAccount: Create a new service account
Returns the created service account including
slug and clientSecret. If the service account already exists, returns { slug } without error.
rotateServiceAccountSecret: Rotate a service account’s client secret
Returns the new
clientSecret.
deleteServiceAccount: Delete a service account
Cache behavior
The module caches client secrets in memory after creation or rotation. This cache is automatically invalidated when:- A service account is deleted (clears secret + permissions cache)
- A service account secret is rotated (clears secret cache)
- A service account is updated (clears permissions cache)
Service Account Example
Org API Key Functions
These functions let a privileged workspace mint and manage org-level API keys on behalf of its users, typically to expose a programmatic key tied to a single product resource (an agent, a workflow, etc.) without granting the user directorgs:apikeys:manage permission.
Security model
The runtime enforces strict isolation so a privileged workspace can only see and manage keys it owns:ownerTypeis force-prefixed with the calling workspace slug (e.g. the DSUL passesagent, the runtime storesagent-factory:agent). The DSUL cannot spoof another workspace’sownerType.listOrgApiKeysforce-prefixes theownerTypefilter the same way, so listings only ever return keys minted by the calling workspace.rotateOrgApiKey/deleteOrgApiKeyfetch the target key first viaGET /v2/orgs/:orgSlug/api-keys/:keyIdand refuse the operation unless the existingownerTypestarts with${workspaceSlug}:.permissionsandscopesare forwarded verbatim to api-gateway. The DSUL is responsible for prefixing them; the runtime then validates each value against the privileged-workspace allowlist (see config below), so a workspace can declare permissions/scopes of other workspaces only if the operator has explicitly listed them.
Configuration
AddserviceAccounts and/or apiKeys blocks to the workspace’s entry in PRIVILEGED_WORKSPACES:
privileges.accessManager body.
If
serviceAccounts is omitted, the workspace cannot mint service-account tokens. If apiKeys is omitted, the workspace cannot create API keys.
createOrgApiKey: Mint a new API key
Returns
{ id, slug, apiKey, name, permissions, expiresAt }. The raw apiKey is shown only on creation; store it immediately.
listOrgApiKeys: List the workspace’s API keys
Returns
{ results: OrgApiKey[], total }. Keys minted by other workspaces in the same org are never returned.
rotateOrgApiKey: Regenerate a key’s secret
Returns the same shape as
createOrgApiKey (with the new raw apiKey). Throws if the key’s ownerType doesn’t start with ${workspaceSlug}:.
deleteOrgApiKey: Revoke a key
Returns
{ success: true }. Like rotateOrgApiKey, refuses if the key isn’t owned by the calling workspace.
Org API Key Example
End-to-end automation that mints, lists and revokes an API key tied to a specific agent (single endpoint exposed via webhook in the agent-factory workspace):Product Bindings Functions
Product bindings associate resources (agents, workflows, etc.) to principals (users, orgs, groups) within a workspace. All binding functions automatically scope queries to the caller’sworkspaceId; it cannot be overridden.
findBindings: Query bindings
Returns an array of binding documents.
findAndCountBindings: Query bindings with total count
findBindings. Returns { items: [...], total: number }.
countBindings: Count matching bindings
Returns a number.
insertBinding: Create a binding
The
workspaceId and workspaceSlug are set automatically from the caller’s context. Emits a runtime.bindings.created event.
A unique constraint prevents duplicate bindings for the same (workspaceId, resourceType, resourceId, principalType, principalId).
updateBinding: Update an existing binding
Only
roleSlug can be updated through this function; other fields are immutable. Emits a runtime.bindings.updated event when at least one binding is modified.
deleteOneBinding: Delete a single binding
Emits a
runtime.bindings.deleted event if a binding was deleted.
deleteManyBindings: Delete multiple bindings
Emits a
runtime.bindings.deleted.many event if any bindings were deleted.
Workspace cleanup
When a workspace is deleted, all its bindings are automatically removed.Access Check Function
ThecheckAccess function provides a high-level access control check that combines three sources: permissions (from run.permissions), scopes (from run.scopes), and bindings (from the product_bindings collection). This replaces the need for complex DSUL-based permission checking.
checkAccess: Check resource access
resourceType/action, it only checks authentication and returns isWorkspaceAdmin.
Return value
The
error object follows the same shape as _auth automations:
Resolution order
-
Authentication: The caller must have a
userId(user session) or anorgSlug(org API key). If neither is present, returnsUnauthorizedimmediately. -
Permissions: Checked from
run.permissions(set by the token’s role). The function checks in order:*withmanage→ workspace admin{workspaceSlug}withmanage→ workspace admin{workspaceSlug}:{resourceType}withmanageor{action}→ access- If no permission matches → returns
Forbiddenimmediately (scopes and bindings are not checked)
-
Scopes: Parsed from
run.scopes(only if permission was granted):*,{workspaceSlug}:*, or{workspaceSlug}:{resourceType}:*→ wildcard scope (all resources){workspaceSlug}:{resourceType}:{id}→ addsidto scoped IDs
-
Bindings: Looked up in
product_bindingsfor the caller’s identity (userId, org, groups). Only checked for single resource mode when the scope doesn’t match. Each matching binding is then evaluated against the requested action through role-based bindings.
Role-based bindings
Bindings can carry aroleSlug to restrict which actions they grant. The role definitions themselves live outside the binding; they must be passed to checkAccess via the roles parameter (typically loaded from config.roles):
checkAccess only stops at the first binding that grants the requested action. If a user has multiple bindings (e.g. reader and editor), the most permissive applicable one wins.
When the binding is granted, the reason field reflects the role: binding:user:editor, binding:org:admin, etc.
Modes
Auth-only (noresourceType, no action):
- Returns
{ granted: true, isWorkspaceAdmin }, which just confirms the caller is authenticated
- Returns
{ granted: false, error: { error: 'Unauthorized', message: 'Authentication required' } }
resourceId provided):
- If wildcard scope →
{ granted: true, reason: 'wildcard-scope', hasWildcardScope: true, isWorkspaceAdmin } - If scoped ID match →
{ granted: true, reason: 'scope', hasWildcardScope: false, isWorkspaceAdmin } - If binding found and the binding’s
roleSlugallows the action →{ granted: true, reason: 'binding:{principalType}' or 'binding:{principalType}:{roleSlug}', hasWildcardScope: false, isWorkspaceAdmin } - Otherwise →
{ granted: false, hasWildcardScope: false, error: { error: 'Forbidden', message: '...' } }
list: true, no resourceId):
- If wildcard scope →
{ granted: true, grantedIds: [], hasWildcardScope: true }(caller sees everything) - Otherwise →
{ granted: true, grantedIds: [...], hasWildcardScope: false }(merged+deduplicated scoped IDs + binding IDs)
resourceType + action, no resourceId, no list):
- Returns
{ granted: true, reason: 'permission', hasWildcardScope, isWorkspaceAdmin }