Skip to main content
Runtime modules are the built-in capabilities you call from automations on the foundation, and the access-manager module is one of them. It provides four groups of functions :
  1. Service account management: Create, rotate, delete service accounts and issue JWT tokens. Restricted to privileged workspaces.
  2. Org API key management: Create, list, rotate, revoke org API keys scoped to the privileged workspace. Restricted to privileged workspaces.
  3. Product bindings: CRUD operations on the product_bindings collection to link resources to users, orgs, or groups. Available to any workspace.
  4. Access checking: High-level checkAccess function that combines permissions, scopes, and bindings to determine if a caller can access a resource. Available to any workspace.
Service-account and org-API-key functions (getServiceAccountToken, createServiceAccount, rotateServiceAccountSecret, deleteServiceAccount, createOrgApiKey, listOrgApiKeys, rotateOrgApiKey, deleteOrgApiKey) are only available to privileged workspaces. A workspace is privileged when either of the following holds:
  • It is listed in the PRIVILEGED_WORKSPACES environment variable (operator-curated, slug-keyed; applies platform-wide), or
  • An organization has granted it privileges via PUT /v2/orgs/{orgSlug}/workspaces/{workspaceIdOrSlug}/privileges (per-org grant, ID-keyed; applies only inside that org’s session).
Binding and access-check functions are available to all workspaces.

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)
Cache invalidation is event-driven and applies to all runtime instances simultaneously.

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 direct orgs:apikeys:manage permission.

Security model

The runtime enforces strict isolation so a privileged workspace can only see and manage keys it owns:
  • ownerType is force-prefixed with the calling workspace slug (e.g. the DSUL passes agent, the runtime stores agent-factory:agent). The DSUL cannot spoof another workspace’s ownerType.
  • listOrgApiKeys force-prefixes the ownerType filter the same way, so listings only ever return keys minted by the calling workspace.
  • rotateOrgApiKey / deleteOrgApiKey fetch the target key first via GET /v2/orgs/:orgSlug/api-keys/:keyId and refuse the operation unless the existing ownerType starts with ${workspaceSlug}:.
  • permissions and scopes are 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

Add serviceAccounts and/or apiKeys blocks to the workspace’s entry in PRIVILEGED_WORKSPACES:
The same shape is accepted by the per-org grant endpoint as the 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’s workspaceId; it cannot be overridden.

findBindings: Query bindings

Returns an array of binding documents.

findAndCountBindings: Query bindings with total count

Same parameters as 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

The checkAccess 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

All parameters are optional. When called without resourceType/action, it only checks authentication and returns isWorkspaceAdmin.

Return value

The error object follows the same shape as _auth automations:

Resolution order

  1. Authentication: The caller must have a userId (user session) or an orgSlug (org API key). If neither is present, returns Unauthorized immediately.
  2. Permissions: Checked from run.permissions (set by the token’s role). The function checks in order:
    • * with manage → workspace admin
    • {workspaceSlug} with manage → workspace admin
    • {workspaceSlug}:{resourceType} with manage or {action} → access
    • If no permission matches → returns Forbidden immediately (scopes and bindings are not checked)
  3. Scopes: Parsed from run.scopes (only if permission was granted):
    • *, {workspaceSlug}:*, or {workspaceSlug}:{resourceType}:* → wildcard scope (all resources)
    • {workspaceSlug}:{resourceType}:{id} → adds id to scoped IDs
  4. Bindings: Looked up in product_bindings for 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 a roleSlug 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):
Resolution rules applied to each candidate binding: 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.
Keep your role catalog in config.roles so it can be referenced consistently from every endpoint that calls checkAccess (and from insertBinding / updateBinding for validation).

Modes

Auth-only (no resourceType, no action):
  • Returns { granted: true, isWorkspaceAdmin }, which just confirms the caller is authenticated
Unauthenticated caller:
  • Returns { granted: false, error: { error: 'Unauthorized', message: 'Authentication required' } }
Single resource (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 roleSlug allows the action → { granted: true, reason: 'binding:{principalType}' or 'binding:{principalType}:{roleSlug}', hasWildcardScope: false, isWorkspaceAdmin }
  • Otherwise → { granted: false, hasWildcardScope: false, error: { error: 'Forbidden', message: '...' } }
List mode (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)
Permission-only (resourceType + action, no resourceId, no list):
  • Returns { granted: true, reason: 'permission', hasWildcardScope, isWorkspaceAdmin }

Access check examples

Guard a specific resource

Check auth + admin status only

List all accessible resources