1. Docs
  2. Hierarchy

Hierarchy

Model real organizational structure inside an Environment: typed nodes, parent-child rules, and role assignments that cascade down the tree.

Overview

A hierarchy is a tree of typed nodes representing the organizational structure inside an Environment: regions, offices, departments, teams, or whatever shape your customers actually have. Every Environment starts with a single root node (flat RBAC); switching to hierarchy mode lets you add child nodes and assign roles at any depth. The shape is yours to define via the Environment's hierarchy_schema; Canopy enforces the schema on every node create and move and walks the tree at evaluate-time to resolve permissions through inheritance. Hierarchy also runs inside the organizations container: with organizations on, each organization is the top of its own tree and the levels you define hang beneath it. If your tenants are whole companies rather than branches of one, see the Organizations page.

Scope

Hierarchies are scoped to an Environment

Each Environment owns its own independent tree, root node, and hierarchy_schema. Sibling envs in the same Application can carry completely different shapes: development can run hierarchy mode while production stays flat, or both can run hierarchy with different node-type vocabularies. The schema lives on environment.settings, not on the Application.

A node created in Development does not appear in Production: every env's tree is its own row setaccess_model is per-env: a development env in hierarchy mode coexists with a production env in flat modeThe Promote / Import flow copies hierarchy nodes between envs as part of the env config snapshot, but role assignments are deliberately left empty for you to configure in the target env. With the organizations container on, only the root node travels: organizations and their trees are tenant data, not configuration

This is what makes safe iteration possible: you can model an entire org tree in Development, test the evaluator against realistic node depths, and only flip Production over when you're confident the shape holds.

View the Environments reference →

Schema & Node Types

The hierarchy_schema defines the rules your tree has to follow. It lists every node_type a node can be (e.g. region, office, department, team), the allowed_children map (which types can sit beneath which), the max_depth, and the root_node_type. The schema is a prerequisite: define it before your first node create, via PATCH /api/v1/hierarchy-schema from a setup script or the Console's enable-hierarchy wizard; otherwise POST /api/v1/nodes refuses with a 400 ("Hierarchy schema is not configured"). Canopy then validates against the schema on every node create and every node move. An invalid parent-child combination or a move that exceeds max_depth is rejected with a 400 before any data changes.

node_type: a free-form string the customer defines (Canopy doesn't interpret it); names starting with canopy: are reserved for Canopy's own typesallowed_children: a map from parent type to the array of child types it can contain; a type mapped to an empty list is a leaf and takes no childrenmax_depth: hard cap on tree depth; protects evaluator cost and UI renderingroot_node_type: the type the root node must carry (typically application or the customer's outermost concept)

The schema is mutable via PATCH .../hierarchy-schema, and the one change it refuses is removing a node type that's currently in use. Other changes, to allowed_children or max_depth, govern nodes created or moved afterwards; existing nodes keep their place even where the new rules would not allow them.

Node Operations

Nodes are managed through the standard env-scoped endpoints: create under any valid parent, move to a new parent if the schema allows, and soft-delete a node together with everything beneath it. When a node moves, every assignment at or below it moves with it, and inheritance recalculates automatically; there is no separate publish step.

POST .../nodes: create a node under a valid parent per the schemaPATCH .../nodes/:id: update a node's name, slug, status (active or inactive) or metadata, or re-parent it with parent_node_id under the same checks as a move; the node type is immutable, and status: deleted is refused in favour of the delete endpointPOST .../nodes/:id/move: re-parent a node; rejected on schema violation or cycleDELETE .../nodes/:id: soft-delete the node and its whole subtree (every node's status becomes deleted) and remove every role assignment made at any of those nodes, so a hidden branch cannot keep granting access. The root cannot be deleted, and an organization node is removed through the organizations API insteadGET .../nodes: one page of the nodes you can see, flat, each with its parent_node_id, access flags and identity counts; GET .../nodes/:id/tree narrows that to a node and its descendants, and GET .../nodes/:id/children to its direct children. Each takes page, take (at most 100), q and sort_by (name or created); read every page and link each node to its parent to rebuild the tree

The Console's Hierarchy page wraps these endpoints behind a drag-and-drop tree editor; client apps can integrate directly via the same routes on the public API surface.

Inheritance & Cascade

When you assign a role to an identity at a node, the role's permissions apply at that node and every descendant. Canopy resolves permissions at evaluate-time by walking up the lineage from the target node to the root, gathering every active assignment along the way (inside an organization, only while the holder's membership there is active), and unioning the permissions:

Direct assignment: the assignment row is on the target node itselfInherited assignment: the assignment lives at an ancestor node and cascades downUnion semantics: multiple roles at multiple ancestors all contribute; the identity ends up with the combined permission set

An identity needs only one assignment at the highest applicable level, with no per-leaf duplication. Assign a Regional Manager role at the North America node and it covers every office, team, and identity beneath it. Move a subtree to a new parent and the assignments come along; the lineage walk picks up the new ancestor's roles automatically.

Reverting to Flat

An Environment in hierarchy mode can collapse back to flat at any time via POST .../revert-to-flat. The operation consolidates surviving assignments at the root node and deletes every non-root node, keeping each surviving assignment's dates rather than activating or extending them, with one exception: an identity holding the same role through several assignments ends up with one that runs from the earliest start to the latest end, which grants the role in any gap between them:

Active assignments are merged to the root with the broadest surviving date window (the union of all effective_from / effective_to bounds for the same identity + role)Scheduled assignments are preserved: the effective_from in the future stays in the future; they don't activate immediately on revertExpired assignments are dropped entirely; they granted no access today and would silently reinstate as unbounded grants if preservedSibling Environments in the same Application are unaffected: the revert is per-env

The response includes assignments_moved, assignments_deduplicated, assignments_expired_dropped, assignments_dropped_scoped, nodes_deleted and organizations_preserved so the admin sees exactly what happened. The revert is refused while any Admin Workspace administrator is scoped to a node in the Environment, because deleting the node would silently take that grant away: re-scope those administrators first. The env's hierarchy_schema is cleared and access_model flips to flat only after the assignment consolidation completes. With the organizations container on, the organizations and their memberships stay; only the trees beneath them go, and assignments at those nodes are dropped rather than widened.

Next Step

Hierarchies define the shape your access policy follows. The next step is the API reference for the node CRUD + evaluate endpoints, which is how your client app actually consumes the structure at runtime.

Environment
API version
v1.0
On this page Was this page helpful?

Tell us how we can improve this guide.