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.

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

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. Canopy 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)allowed_children: a map from parent type to the array of child types it can containmax_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, but Canopy refuses changes that would invalidate existing nodes — you can extend the vocabulary freely, you can't retroactively delete a node type that's currently in use.

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, delete leaves (or whole subtrees if the dashboard's confirm flow accepts 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 name / slug / metadata; type changes are rejected if they'd break parent-child rulesPOST .../nodes/:id/move — re-parent a node; rejected on schema violation or cycleDELETE .../nodes/:id — delete the node (cascades to descendants + their assignments via FK rules)

The dashboard'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, 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 — 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 — but it does not silently change anyone's effective access:

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, and nodes_deleted so the admin sees exactly what happened. The env's hierarchy_schema is cleared and access_model flips to flat only after the assignment consolidation completes.

Next Step

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