> ## Documentation Index
> Fetch the complete documentation index at: https://prismeai-legacy.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Access Manager Module

> Manage service account tokens, product bindings, and access checks at runtime

The **access-manager** module provides three groups of functions :

1. **Service account management** — Create, rotate, delete service accounts and issue JWT tokens. Restricted to **privileged workspaces** defined via `PRIVILEGED_WORKSPACES`.
2. **Product bindings** — CRUD operations on the `product_bindings` collection to link resources to users, orgs, or groups. Available to **any workspace**.
3. **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**.

<Warning>
  Service account functions (`getServiceAccountToken`, `createServiceAccount`, `rotateServiceAccountSecret`, `deleteServiceAccount`) are only available to workspaces listed in the `PRIVILEGED_WORKSPACES` environment variable. Binding and access check functions are available to all workspaces.
</Warning>

## Service Account Functions

### getServiceAccountToken — Get a JWT token for a service account

```yaml theme={null}
- run:
    module: access-manager
    function: getServiceAccountToken
    parameters:
      orgSlug: "{{orgSlug}}"
      serviceAccountSlug: "{{saSlug}}"
      create: true
      expiresIn: 3600
    output: tokenResult
```

| Parameter            | Type    | Required | Description                                                                                                                                              |
| -------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgSlug`            | string  | yes      | Organization slug                                                                                                                                        |
| `serviceAccountSlug` | string  | yes      | Service account slug                                                                                                                                     |
| `create`             | boolean | no       | When `true`, creates the service account if it doesn't exist and rotates the secret if needed. When `false` or omitted, only works with a cached secret. |
| `name`               | string  | no       | Display name for the service account (used on creation)                                                                                                  |
| `roleSlug`           | string  | no       | Role to assign. Must be in the workspace's `allowedRoleSlugs`. Defaults to the workspace's `defaultRoleSlug`.                                            |
| `expiresIn`          | number  | no       | Token TTL in seconds                                                                                                                                     |

Returns the token response including `accessToken`, `tokenType`, `expiresAt`, `permissions`, and `scopes`.

### createServiceAccount — Create a new service account

```yaml theme={null}
- run:
    module: access-manager
    function: createServiceAccount
    parameters:
      orgSlug: "{{orgSlug}}"
      serviceAccountSlug: "{{saSlug}}"
      name: "My Agent"
      roleSlug: "agent-standard"
    output: result
```

| Parameter            | Type   | Required | Description                                      |
| -------------------- | ------ | -------- | ------------------------------------------------ |
| `orgSlug`            | string | yes      | Organization slug                                |
| `serviceAccountSlug` | string | yes      | Service account slug                             |
| `name`               | string | no       | Display name                                     |
| `roleSlug`           | string | no       | Role to assign (validated against allowed roles) |

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

```yaml theme={null}
- run:
    module: access-manager
    function: rotateServiceAccountSecret
    parameters:
      orgSlug: "{{orgSlug}}"
      serviceAccountSlug: "{{saSlug}}"
    output: result
```

| Parameter            | Type   | Required | Description          |
| -------------------- | ------ | -------- | -------------------- |
| `orgSlug`            | string | yes      | Organization slug    |
| `serviceAccountSlug` | string | yes      | Service account slug |

Returns the new `clientSecret`.

### deleteServiceAccount — Delete a service account

```yaml theme={null}
- run:
    module: access-manager
    function: deleteServiceAccount
    parameters:
      orgSlug: "{{orgSlug}}"
      serviceAccountSlug: "{{saSlug}}"
    output: result
```

| Parameter            | Type   | Required | Description          |
| -------------------- | ------ | -------- | -------------------- |
| `orgSlug`            | string | yes      | Organization slug    |
| `serviceAccountSlug` | string | yes      | Service account slug |

## 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

```yaml theme={null}
slug: get-agent-token
name: Get Agent Token
do:
  # Create (or reuse) a service account and get a JWT token
  - run:
      module: access-manager
      function: getServiceAccountToken
      parameters:
        orgSlug: "{{orgSlug}}"
        serviceAccountSlug: "agent-{{agentId}}"
        name: "Agent {{agentId}}"
        create: true
        expiresIn: 3600
      output: tokenResult

  # Use the token to call an API
  - fetch:
      url: "{{config.apiUrl}}/resources"
      method: GET
      headers:
        Authorization: "Bearer {{tokenResult.accessToken}}"
      output: resources
```

***

## 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

```yaml theme={null}
- run:
    module: access-manager
    function: findBindings
    parameters:
      query:
        resourceType: agents
        principalType: user
        principalId: "{{userId}}"
      options:
        pagination:
          limit: 50
          page: 0
        sort:
          createdAt: desc
    output: bindings
```

| Parameter            | Type   | Required | Description                                                                                                            |
| -------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `query`              | object | yes      | Filter fields matching `ProductBinding` (e.g. `resourceType`, `resourceId`, `principalType`, `principalId`, `orgSlug`) |
| `options.pagination` | object | no       | `{ page, skip, limit }`                                                                                                |
| `options.sort`       | object | no       | Sort fields (e.g. `{ createdAt: 'desc' }`)                                                                             |
| `options.fields`     | array  | no       | Fields to return                                                                                                       |

Returns an array of binding documents.

### findAndCountBindings — Query bindings with total count

```yaml theme={null}
- run:
    module: access-manager
    function: findAndCountBindings
    parameters:
      query:
        resourceType: agents
      options:
        pagination:
          limit: 20
          page: 0
    output: result
# result.items = [...], result.total = 42
```

Same parameters as `findBindings`. Returns `{ items: [...], total: number }`.

### countBindings — Count matching bindings

```yaml theme={null}
- run:
    module: access-manager
    function: countBindings
    parameters:
      query:
        resourceType: agents
        orgSlug: "{{orgSlug}}"
    output: count
```

| Parameter | Type   | Required | Description   |
| --------- | ------ | -------- | ------------- |
| `query`   | object | yes      | Filter fields |

Returns a number.

### insertBinding — Create a binding

```yaml theme={null}
- run:
    module: access-manager
    function: insertBinding
    parameters:
      data:
        resourceType: agents
        resourceId: "{{agentId}}"
        principalType: user
        principalId: "{{targetUserId}}"
        orgSlug: "{{orgSlug}}"
        grantedBy: "{{userId}}"
        email: "{{targetEmail}}"
    output: result
# result.acknowledged = true, result.insertedId = "..."
```

| Parameter            | Type           | Required | Description                                                                                                                                                                                     |
| -------------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data.resourceType`  | string         | yes      | Resource type (e.g. `agents`, `workflows`)                                                                                                                                                      |
| `data.resourceId`    | string         | yes      | Resource identifier                                                                                                                                                                             |
| `data.principalType` | string         | yes      | `user`, `org`, or `group`                                                                                                                                                                       |
| `data.principalId`   | string         | yes      | Principal identifier                                                                                                                                                                            |
| `data.orgSlug`       | string         | yes      | Organization slug                                                                                                                                                                               |
| `data.grantedBy`     | string         | yes      | User who granted the binding                                                                                                                                                                    |
| `data.email`         | string         | no       | Email for user bindings                                                                                                                                                                         |
| `data.roleSlug`      | string \| null | no       | Optional role slug controlling which actions this binding grants. See [Role-based bindings](#role-based-bindings). When `null` or omitted, the binding grants every action **except `delete`**. |

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

```yaml theme={null}
- run:
    module: access-manager
    function: updateBinding
    parameters:
      query:
        resourceType: agents
        resourceId: "{{agentId}}"
        principalType: user
        principalId: "{{targetUserId}}"
      data:
        roleSlug: editor
    output: result
# result.matchedCount = 1, result.modifiedCount = 1
```

| Parameter       | Type           | Required | Description                                                                                                  |
| --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `query`         | object         | yes      | Filter to match the binding(s) to update                                                                     |
| `data.roleSlug` | string \| null | no       | New role slug to apply. Pass `null` to clear the role (binding will then grant all actions except `delete`). |

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

```yaml theme={null}
- run:
    module: access-manager
    function: deleteOneBinding
    parameters:
      query:
        resourceType: agents
        resourceId: "{{agentId}}"
        principalType: user
        principalId: "{{targetUserId}}"
    output: result
# result.deletedCount = 1
```

| Parameter | Type   | Required | Description                           |
| --------- | ------ | -------- | ------------------------------------- |
| `query`   | object | yes      | Filter to match the binding to delete |

Emits a `runtime.bindings.deleted` event if a binding was deleted.

### deleteManyBindings — Delete multiple bindings

```yaml theme={null}
- run:
    module: access-manager
    function: deleteManyBindings
    parameters:
      query:
        resourceType: agents
        resourceId: "{{agentId}}"
    output: result
# result.deletedCount = 5
```

| Parameter | Type   | Required | Description                        |
| --------- | ------ | -------- | ---------------------------------- |
| `query`   | object | yes      | Filter to match bindings to delete |

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

```yaml theme={null}
- run:
    module: access-manager
    function: checkAccess
    parameters:
      resourceType: agents
      resourceId: "{{agentId}}"
      action: read
    output: access
```

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

| Parameter      | Type    | Required | Description                                                                                                                                                                                                                                                 |
| -------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resourceType` | string  | no       | Resource type (e.g. `agents`, `workflows`). Must be set together with `action`.                                                                                                                                                                             |
| `resourceId`   | string  | no       | Specific resource ID. Requires `resourceType`.                                                                                                                                                                                                              |
| `action`       | string  | no       | Action to check (typically `read`, `write`, `share`, `delete`, `manage`, but any string is accepted — must match the keys in `roles[*].permissions`). Must be set together with `resourceType`.                                                             |
| `list`         | boolean | no       | When `true`, returns the list of granted resource IDs instead of a single grant                                                                                                                                                                             |
| `roles`        | object  | no       | Role definitions for binding role enforcement. `Record<string, { name?: string, permissions: string[] }>`. **Required as soon as any matching binding has a `roleSlug`** — otherwise `checkAccess` throws. See [Role-based bindings](#role-based-bindings). |

### Return value

| Field              | Type      | Description                                                                                                                                                                                                                                                                                |
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `granted`          | boolean   | Whether access is granted                                                                                                                                                                                                                                                                  |
| `reason`           | string    | Why access was granted: `permission`, `wildcard-scope`, `scope`, or `binding:{principalType}` (e.g. `binding:user`, `binding:org`, `binding:group`). When the matching binding carries a `roleSlug`, the reason becomes `binding:{principalType}:{roleSlug}` (e.g. `binding:user:editor`). |
| `grantedIds`       | string\[] | Only in list mode — merged set of IDs from scopes and bindings                                                                                                                                                                                                                             |
| `hasWildcardScope` | boolean   | `true` if the caller has wildcard scope (sees all resources of this type)                                                                                                                                                                                                                  |
| `isWorkspaceAdmin` | boolean   | `true` if the caller has `*` or `{workspaceSlug}` manage permission                                                                                                                                                                                                                        |
| `error`            | object    | Only when `granted` is `false` — contains `error` (code) and `message` (human-readable)                                                                                                                                                                                                    |

The `error` object follows the same shape as `_auth` automations:

| `error.error`  | `error.message`                                                               | When                                                   |
| -------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------ |
| `Unauthorized` | `Authentication required`                                                     | No authenticated user (no `userId` and no org API key) |
| `Forbidden`    | `Access denied: missing permission '{workspaceSlug}:{resourceType}:{action}'` | User is authenticated but has no matching permission   |

### Resolution order

0. **Authentication** — The caller must have a `userId` (user session) or an `orgSlug` (org API key). If neither is present, returns `Unauthorized` immediately.

1. **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)

2. **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

3. **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).

### 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`):

```yaml theme={null}
- run:
    module: access-manager
    function: checkAccess
    parameters:
      resourceType: agents
      resourceId: "{{agentId}}"
      action: write
      roles:
        owner:
          name: Owner
          permissions: [read, write, share, delete]
        admin:
          name: Admin
          permissions: [read, write, share]
        editor:
          name: Editor
          permissions: [read, write]
        reader:
          name: Reader
          permissions: [read]
    output: access
```

Resolution rules applied to each candidate binding:

| Binding `roleSlug`  | `roles` parameter         | Behavior                                                                                                                                                 |
| ------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null` / unset      | (any)                     | Binding grants **every action except `delete`**. Used for legacy bindings or "full collaborator" semantics.                                              |
| set (e.g. `editor`) | provided, slug exists     | Action must be in `roles[slug].permissions` — otherwise the binding is ignored and `checkAccess` continues to the next binding (or returns `Forbidden`). |
| set                 | not provided **at all**   | `checkAccess` **throws** — passing `roles` is required as soon as any binding could be role-typed.                                                       |
| set                 | provided but slug missing | The binding is ignored (treated as having no permissions).                                                                                               |

`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.

<Tip>
  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).
</Tip>

### Modes

**Auth-only** (no `resourceType`, no `action`):

* Returns `{ granted: true, isWorkspaceAdmin }` — 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

```yaml theme={null}
- run:
    module: access-manager
    function: checkAccess
    parameters:
      resourceType: agents
      resourceId: "{{agentId}}"
      action: read
    output: access

# access.error is set when granted=false (UNAUTHORIZED or FORBIDDEN)
- conditions:
    "{{access.error}}":
        - break:
            automation: true
            output:
              error: "{{access.error.error}}"
              message: "{{access.error.message}}"
```

#### Check auth + admin status only

```yaml theme={null}
- run:
    module: access-manager
    function: checkAccess
    output: access

# access.granted = true if authenticated
# access.isWorkspaceAdmin = true if user has * or workspace-level manage
```

#### List all accessible resources

```yaml theme={null}
- run:
    module: access-manager
    function: checkAccess
    parameters:
      resourceType: agents
      action: read
      list: true
    output: access

# If access.hasWildcardScope is true, query all agents without filter
# Otherwise, filter agents by access.grantedIds
- conditions:
    "{{access.hasWildcardScope}}":
        - fetch:
            url: "{{config.apiUrl}}/agents"
            output: agents
    default:
        - fetch:
            url: "{{config.apiUrl}}/agents?ids={{access.grantedIds | join ','}}"
            output: agents
```
