For the complete documentation index, see llms.txt. This page is also available as Markdown.

External Principals

Entity-anchored, role-scoped tool access for external users driving agent sessions.

External principals let you bind an external user identity to a workspace entity and scope which tools, skills, and data that user's agent session can access. This is designed for scenarios where an end user - such as a clinician, care coordinator, or partner employee - drives a conversation through the agent and should only see or invoke resources their role permits.

How It Works

External principals connect three concepts:

  1. Role registry - Each workspace defines a set of internal roles (normalized lowercase slugs) that map to external role names from the customer's identity system. Roles are workspace-scoped and deduplicated at the registry level.

  2. Role assignments - An assignment record binds an external user (identified by a subject key such as an email address, and optionally a vendor-specific subject ID) to a workspace entity and one or more internal roles. Assignments carry a status (active, suspended, or revoked) and track how they were provisioned (roster sync or just-in-time). Role verification status is recorded separately - a session opened with unverified roles can behave differently from one with verified roles.

  3. Role grants - Grants map a registry role to a specific resource (integration endpoint, skill, or knowledge base scope) with a read or write access level. Grants are immutable - changes insert a new active row and mark the prior one as superseded, so the authorization state at any past point in time is reconstructable for audit.

Management Endpoints

Each of the three concepts has its own workspace-scoped resource family.

Roles

Register the workspace's internal roles and their external role name mappings.

Create External Role

post
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Body
namestring · min: 1 · max: 64RequiredPattern: ^[a-z][a-z0-9_]{0,63}$
external_namestring · min: 1 · max: 256Required
descriptionstring · max: 2048 · nullableOptional
Responses
201

Successful Response

application/json

Wire shape for read + create responses.

idstring · uuidRequired
namestringRequired
external_namestringRequired
descriptionstring · nullableOptional
created_atstring · date-timeRequired
updated_atstring · date-timeRequired
post/v1/{workspace_id}/external-roles

List External Roles

get
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Query parameters
sort_bystring[]OptionalDefault: []
limitinteger · max: 200OptionalDefault: 50
continuation_tokenanyOptional
Responses
200

Successful Response

application/json
has_morebooleanRequired
continuation_tokenanyOptional
get/v1/{workspace_id}/external-roles

Individual roles are read and removed by ID at /v1/{workspace_id}/external-roles/{role_id}.

Role Assignments

Bind an external user (subject key, optional subject ID) to a workspace entity and one or more roles. The PUT upserts the assignment for a subject.

Upsert External Role Assignment

put
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Body
sourcestring · min: 1 · max: 128Required
external_subject_keystring · min: 1 · max: 512Required
external_subject_idstring · max: 512 · nullableOptional
entity_idstring · uuidRequired
display_namestring · max: 512 · nullableOptional
rolesstring[] · max: 64Optional
roles_verifiedbooleanOptionalDefault: false
provisioned_viastring · enumRequiredPossible values:
Responses
200

Successful Response

application/json
idstring · uuidRequired
sourcestringRequired
external_subject_keystringRequired
external_subject_idstring · nullableOptional
entity_idstring · uuidRequired
display_namestring · nullableOptional
rolesstring[]Required
roles_verifiedbooleanRequired
statusstring · enumRequiredPossible values:
provisioned_viastring · enumRequiredPossible values:
last_verified_atstring · date-time · nullableOptional
created_atstring · date-timeRequired
updated_atstring · date-timeRequired
put/v1/{workspace_id}/external-role-assignments

List External Role Assignments

get
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Query parameters
sort_bystring[]OptionalDefault: []
limitinteger · max: 200OptionalDefault: 50
continuation_tokenanyOptional
Responses
200

Successful Response

application/json
has_morebooleanRequired
continuation_tokenanyOptional
get/v1/{workspace_id}/external-role-assignments

Individual assignments are read and removed by ID at /v1/{workspace_id}/external-role-assignments/{assignment_id}.

Role Grants

Map a role to a resource with a read or write access level.

Create Role Grant

post
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Body
role_idstring · uuidRequired
resource_typestring · enumRequiredPossible values:
resource_keystring · min: 1 · max: 512Required
accessstring · enumRequiredPossible values:
param_bindingstring · max: 256 · nullableOptional
Responses
201

Successful Response

application/json
idstring · uuidRequired
role_idstring · uuidRequired
resource_typestring · enumRequiredPossible values:
resource_keystringRequired
accessstring · enumRequiredPossible values:
param_bindingstring · nullableOptional
statusstring · enumRequiredPossible values:
changed_bystring · uuid · nullableOptional
created_atstring · date-timeRequired
post/v1/{workspace_id}/role-grants

List Role Grants

get
Authorizations
AuthorizationstringRequired

API key issued via POST /v1/{workspace_id}/api-keys. Pass the returned api_key value as a Bearer token.

Path parameters
workspace_idstring · uuidRequired
Query parameters
sort_bystring[]OptionalDefault: []
limitinteger · max: 200OptionalDefault: 50
continuation_tokenanyOptional
statusstring · enum · nullableOptionalPossible values:
Responses
200

Successful Response

application/json
has_morebooleanRequired
continuation_tokenanyOptional
get/v1/{workspace_id}/role-grants

Individual grants are read and removed by ID at /v1/{workspace_id}/role-grants/{grant_id}. Because grants are immutable, removing a grant supersedes it rather than erasing the audit history.

Session Resolution

When a session opens anchored to an entity that has an active role assignment, the platform resolves a session principal. The principal carries:

  • The external user's identity (subject key and optional subject ID)

  • The union of roles from the assignment

  • The allow-grants derived from those roles

  • The workspace's set of restricted resources (any resource referenced by any active grant)

This resolution happens once at session open. The principal is then used for two purposes throughout the session:

Grant Gating

A resource is restricted if any active grant in the workspace references it. Restriction is a workspace-level fact, not a per-principal one. When a tool call targets a restricted resource, the platform checks whether the principal's roles grant the required access level:

  • A write grant implies read (you can read what you can write)

  • A read grant does not imply write

  • Access is allow-union across all of the principal's roles - there is no deny

Unrestricted resources (those with no grant rows in the workspace) remain workspace-open. Workspaces that define no grants are completely unaffected.

Identity Injection

For integrations that use token exchange authentication, you can declare identity bindings that map authentication parameters to the principal's verified identity attributes (subject key or subject ID). At dispatch time, the bound values are resolved from the principal and injected into the token exchange request. The model never supplies these values - they are stripped from the tool schema presented to the agent and removed from any model-supplied parameters before execution.

This closes the per-user identity impersonation hole: the agent cannot supply or override a bound identity parameter, and a missing principal value fails the call closed rather than proceeding without identity.

Param Bindings

Grants can carry an optional parameter binding constraint (for example, scoping a provider ID parameter to the principal's subject ID). When a grant includes a param binding, the bound value is resolved from the principal and injected into the call parameters at dispatch time. A binding that cannot resolve from the principal fails the call closed.

Integration with Approval Gates

On text channels, the approval gate runs at dispatch, so an approval-gated write can be parked for operator review before grant gating is evaluated. The principal's grants are enforced when the write actually executes: if an operator approves a call the principal's grants deny, the write is still refused at execution. A grant-denied call may therefore appear in the approval queue, but it can never execute.

Sessions Without a Principal

Sessions anchored to entities without an active role assignment proceed without a principal. All tools and resources remain workspace-open, and the session behaves exactly as it did before external principals were introduced. This is the expected path for patient-facing sessions and any workspace that has not configured external roles.

Identity Bindings on Custom Token Exchange

When configuring a custom token exchange integration, the identity_bindings field maps declared parameter names to principal attributes:

  • principal.subject_key - the external user's primary identity (typically an email address)

  • principal.subject_id - the external user's vendor-specific identifier (when available)

For deployments that only need to bind the authenticated external-user session subject into token exchange auth, use external_user.subject_key as described in External User Subject-Key Binding. Use principal.* when the binding must come from materialized External Principals role assignments.

Each bound parameter name must reference a parameter declared on the integration's token exchange headers or body fields. Bindings for undeclared parameters are rejected at configuration time.

A bound parameter is:

  • Removed from the tool schema the agent sees

  • Stripped from any model-supplied parameters at dispatch

  • Injected from the verified principal before the token exchange runs

If the principal does not have a value for a bound attribute (for example, principal.subject_id when no subject ID was recorded), the call fails closed rather than proceeding with an absent identity.

Example: Per-Clinician Access

External principals are the mechanism for embedding an agent that serves many end users under one workspace, where each user must only reach what their role allows - for example a clinician-support agent embedded in a partner's application, where each clinician holds one or more roles and should only invoke the tools and read the data their roles permit.

The flow:

  1. The external system authenticates the end user and asserts their identity to obtain a short-lived external-user session (see External User Subject-Key Binding). The asserted subject key is verified against the materialized entity before a token is issued - a model or a confused caller cannot select an arbitrary user.

  2. The session opens anchored to that user's entity. Their active role assignment loads a principal carrying the union of their roles' grants.

  3. As the agent calls tools, each call is gated by the principal's grants, and identity-bound parameters (such as the clinician's own provider ID) are injected from the verified principal - never from model-supplied input.

Because the scoping is per end user, this is distinct from the workspace-scoped MCP Server, which serves an entire workspace to a single credential and is not per-user scoped.

Last updated

Was this helpful?