1. Docs
  2. Assign Roles

Assign Roles

Give identities the access a role bundles — one identity at a time, or many at once.

Overview

An assignment is the link between a role and an identity. Until a role is assigned to someone, nothing it bundles applies to anyone. Each assignment is a three-part record: identity_id, role_id, and node_id — the identity getting access, the role being granted, and the node in your Environment's hierarchy where the role takes effect. In flat RBAC the node_id is always your Environment's root, so the visible decision is just who and which role. Before you can assign a role, the identity has to exist at the Account and have an active AppMembership for the Application — see the Identities overview for the three-layer model.

Scope

Role assignments are scoped to an Environment

When you assign a role to an identity at a node, the assignment lives in the active Environment — the one selected on the Tenant › Applications page. Identities themselves are shared across every Environment in your Account, but the rows that say "this identity holds this role at this node" are not — each Environment keeps its own.

A role assignment made in Development doesn't follow the identity to ProductionEach new Environment starts with zero assignments — Promote copies permissions, roles, and hierarchy across, but never assignmentsTime bounds (effective_from / effective_to) apply per-env — an assignment can be active in one env and expired in another

This is what makes safe iteration possible — you can hand out roles freely in Development to test invite flows or evaluator behavior, with zero risk of granting access in Production by accident.

View the Environments reference →

What an assignment grants

When you assign a role, the identity gains every permission the role bundles — at the target node and at every descendant via cascade. There's no separate publish step or cache flush:

Permissions are computed at evaluate-time: the assignment row is the source of truth, so changes are live the moment they're saved.Cascade is automatic: an assignment at a parent node grants the role at every descendant node. In flat RBAC this is the entire Environment by definition; in hierarchy mode it's the subtree below the chosen node.Changes are live for evaluate-time API checks immediately. If your application uses the permissions scope and embeds permissions in JWTs, those refresh on the next token issuance.

If you ever need to revoke, removing the assignment row stops the cascade just as immediately.

Under the hood, Canopy evaluates access by walking up the node's lineage, collecting every active assignment along the way, and unioning the permissions from each role. The evaluate endpoint is the canonical source of truth at any moment.

Examples

Flat RBAC

Assign the role Manager to Alice. In flat RBAC every assignment lands at your Environment's single root node:

Acme Production

Alice now has Manager permissions across your entire Environment — there's no narrower scope to choose.

Hierarchy

Assign the role Store Manager to Alice at the node Store #42:

Store #42
├── Electronics
├── Clothing
└── Warehouse

Alice now has Store Manager permissions at every node in that subtree:

Store #42ElectronicsClothingWarehouse

Multiple roles per identity

An identity can hold many roles at the same node. The unique constraint is the three-tuple (identity_id, role_id, node_id) — not the two-tuple (identity_id, node_id) — so you can stack a Manager role on top of a Member role for the same person at the same place. Permissions union across roles, so the identity's effective access is the combination of every role they hold. There is no implicit role hierarchy: assigning Admin doesn't auto-replace Member; both rows live independently and both contribute to the union.

Assignments are additive — removing one role doesn't affect any other roles the same identity holds.

Time-bound access

Every assignment carries optional effective_from and effective_to ISO-8601 timestamps. Leave them blank for permanent, immediate access. Set effective_from in the future to schedule access (the assignment exists but doesn't grant permissions yet); set effective_to to schedule automatic expiration. The dashboard's assignments view labels each row as Active, Scheduled, or Expired based on the current time, and the API's evaluate endpoint honors the same boundaries.