API Walkthrough
Take an empty Environment to a passing permission check over the API, with plain curl: an API key, a permission vocabulary, a role, an identity, an assignment, and the evaluation your app runs in production.
Prerequisites
Provisioning starts in the Developer Console, where you create your credentials. Everything after these three steps can be driven over the API.
In the Console
cnpy_-prefixed secret is shown exactly once. Store it as CANOPY_API_KEY.API keys are Environment-scoped: a key created in Development can never touch Production. The full credential map (which integration scenario needs which credential) is on Keys & Credentials.
API walkthrough
This takes an empty Environment to a passing permission check. Every request goes to your API origin with the key in the X-API-Key header. Single-resource responses arrive as { "data": ... }, collections as { "items": [...] }, and errors as { "error": ... }, the same envelope on every endpoint. The steps use plain curl so they work from any language; on Node.js or NestJS, the official SDKs wrap the auth header, envelope, retries, and pagination for you.
1. Register your permission vocabulary
Permissions are the keys your application checks for: lowercase, dot-separated, in resource.action form such as invoice.read. Each needs a key, a display name and a category; description is optional. Register them in batches:
curl -X POST https://auth.canopy-io.com/api/v1/permissions \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "permissions": [ { "key": "invoice.read", "name": "View invoices", "category": "Invoices" }, { "key": "invoice.approve", "name": "Approve invoices", "category": "Invoices" } ] }'
2. Create a role and attach permissions
Create the role first (its id comes back in data.id), then set the permission keys it grants:
curl -X POST https://auth.canopy-io.com/api/v1/roles \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Invoice Manager", "description": "Read and approve invoices" }' curl -X PUT https://auth.canopy-io.com/api/v1/roles/$ROLE_ID/permissions \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "permission_keys": ["invoice.read", "invoice.approve"] }'
3. Create an identity
The end user being authorized. Keep the returned data.id:
curl -X POST https://auth.canopy-io.com/api/v1/identities \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "ada@acme.com", "first_name": "Ada", "last_name": "Lovelace" }'
4. Find the root hierarchy node
Every Environment starts with a root node, and assignments attach at a node. List the nodes and keep the id of the one whose parent_node_id is null; a new Environment has only the root:
curl -H "X-API-Key: $CANOPY_API_KEY" https://auth.canopy-io.com/api/v1/nodes # → { "items": [ { "id": "<ROOT_NODE_ID>", "parent_node_id": null, ... } ], "pagination": { ... } }
5. Assign the role at the root
Assignments cascade to every descendant of their node, so assigning at the root grants the role everywhere, exactly what a flat-RBAC setup wants:
curl -X POST https://auth.canopy-io.com/api/v1/assignments \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identity_id": "'"$IDENTITY_ID"'", "role_id": "'"$ROLE_ID"'", "node_id": "'"$ROOT_NODE_ID"'" }'
6. Evaluate: the question your app asks in production
scope: "app_wide" asks “does this identity hold the permission anywhere?”; scope: "node" with a node_id anchors the check to one part of the tree. An app-wide answer is not tied to a node, so it names no node and no roles; a node-scoped check also returns effective_node_id and the ids of the roles that granted it in granting_roles:
curl -X POST https://auth.canopy-io.com/api/v1/permissions/evaluate \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identity_id": "'"$IDENTITY_ID"'", "permission": "invoice.approve", "scope": "app_wide" }' # → { # "data": { # "allowed": true, # "permission": "invoice.approve", # "scope_evaluated": "app_wide", # "effective_node_id": null, # "granting_roles": [], # "denial_reason": null, # "deny_id": null # } # }
7. Optional: subscribe to change events
Webhooks fire on catalog event types: the strings must match the catalog verbatim, or use the wildcard "*". Fetch the full catalog live from GET /api/v1/webhooks/event-types, or read Webhook Event Types:
curl -X POST https://auth.canopy-io.com/api/v1/webhooks \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/access-changes", "event_types": ["assignment.created", "assignment.removed"] }'
That's the whole loop: vocabulary, role, identity, assignment, evaluation. From here, the API Reference documents every endpoint the specs enumerate, and Webhook Event Types lists every event string you can subscribe to.
Tell us how we can improve this guide.