Assigning Roles & Limiting Permissions
This guide shows how to create a role, assign permissions to it, attach it to a member, and what access that member gets as a result — both on the server (API) and in the UI (sidebar, buttons).
How It Works End-to-End
Section titled “How It Works End-to-End”1. Create a role record with a permissions JSON array2. Assign the role to a member via member_role3. At login, get_auth_state_service loads the permissions into Session4. Server: Resource::has_instance_permission / has_collection_permission enforces access5. Client: ClientSession.has_permission() controls what the sidebar and buttons showStep 1 — Create a Role
Section titled “Step 1 — Create a Role”Insert a row into the role table with a permissions JSONB array:
INSERT INTO role (id, organization_id, name, description, permissions)VALUES ( gen_random_uuid(), '<your-org-id>', 'Agent Manager', 'Can view, create, and manage agents and knowledge bases', '[ "Agent:Collection:List", "Agent:Collection:Create", "Agent:Instance:View", "Agent:Instance:Update", "Agent:Instance:Delete", "Knowledge:Collection:List", "Knowledge:Collection:Create", "Knowledge:Instance:View", "Knowledge:Instance:Update", "Knowledge:Instance:Delete" ]'::jsonb);Permission strings follow the format Resource:Level:Variant. See the full list in ABAC Authorization → All 15 Resources.
Step 2 — Assign the Role to a Member
Section titled “Step 2 — Assign the Role to a Member”INSERT INTO member_role (id, member_id, role_id)VALUES ( gen_random_uuid(), '<member-id>', -- from the member table '<role-id>' -- from the role table);A member can hold multiple roles. Their effective permissions are the union of all assigned roles.
Step 3 — What the Member Can Access
Section titled “Step 3 — What the Member Can Access”After the member logs in (or their session refreshes), Session.permissions contains the loaded permissions. Here’s what the “Agent Manager” example grants:
Server-side (API)
Section titled “Server-side (API)”| Endpoint | Access |
|---|---|
GET /api/agents | ✅ Allowed (Agent:Collection:List) |
POST /api/agents | ✅ Allowed (Agent:Collection:Create) |
GET /api/agents/{id} | ✅ Allowed (Agent:Instance:View) |
PUT /api/agents/{id} | ✅ Allowed (Agent:Instance:Update) |
DELETE /api/agents/{id} | ✅ Allowed (Agent:Instance:Delete) |
GET /api/analyzers | ❌ 403 Forbidden |
GET /api/calls | ❌ 403 Forbidden |
| Any other resource | ❌ 403 Forbidden |
UI-side (sidebar)
Section titled “UI-side (sidebar)”| Nav link | Visible |
|---|---|
| Dashboard | ✅ Always visible |
| Calls | ✅ Always visible |
| Contacts | ✅ Always visible |
| Agents | ✅ Has Agent:Collection:List |
| Knowledge | ✅ Has Knowledge:Collection:List |
| Messaging | ❌ No Message:Collection:List |
| Phones | ❌ No Phone:Collection:List |
| Analyzers | ❌ No Analyzer:Collection:List |
| Text Agents | ❌ No TextAgent:Collection:List |
| To-Do Types | ❌ No TodoType:Collection:List |
| To-Dos | ❌ No Todo:Collection:List |
| Settings | ✅ Always visible |
Role Examples
Section titled “Role Examples”Read-Only Analyst
Section titled “Read-Only Analyst”Can view calls and contacts; cannot modify anything.
'["Call:Collection:List", "Call:Instance:View", "Contact:Collection:List", "Contact:Instance:View"]'::jsonbSupport Agent
Section titled “Support Agent”Can manage contacts and view calls; no access to AI configuration.
'[ "Contact:Collection:List", "Contact:Collection:Create", "Contact:Instance:View", "Contact:Instance:Update", "ContactNote:Collection:List", "ContactNote:Collection:Create", "ContactNote:Instance:View", "ContactNote:Instance:Update", "Call:Collection:List", "Call:Instance:View", "Todo:Collection:List", "Todo:Instance:View"]'::jsonbAI Admin
Section titled “AI Admin”Full access to all AI configuration; no access to contacts or calls.
'[ "Agent:Collection:List", "Agent:Collection:Create", "Agent:Instance:View", "Agent:Instance:Update", "Agent:Instance:Delete", "Analyzer:Collection:List", "Analyzer:Collection:Create", "Analyzer:Instance:View", "Analyzer:Instance:Update", "Analyzer:Instance:Delete", "Knowledge:Collection:List", "Knowledge:Collection:Create", "Knowledge:Instance:View", "Knowledge:Instance:Update", "Knowledge:Instance:Delete", "TextAgent:Collection:List", "TextAgent:Collection:Create", "TextAgent:Instance:View", "TextAgent:Instance:Update", "TextAgent:Instance:Delete"]'::jsonbOwnership-Based Restrictions
Section titled “Ownership-Based Restrictions”For ContactNote, the *Own permission variants restrict access to records the member authored:
-- This member can only list and view notes they wrote'["ContactNote:Collection:ListOwn", "ContactNote:Instance:ViewOwn"]'::jsonbWith ListOwn, the server filters the query to author_id = session.user.id. With ViewOwn, has_instance_permission compares session.user.id against note.author_id — the check passes only for the author.
Access Level Summary
Section titled “Access Level Summary”| Who | Server behavior | UI behavior |
|---|---|---|
| Super-admin | Bypasses all checks, cross-org | has_permission() always true |
| Org owner | Bypasses all checks within org | has_permission() always true |
| Member with roles | ABAC on every endpoint | Only sees what their permissions allow |
| Member with no roles | 403 on all gated endpoints | Sees only always-visible links |
Revoking Access
Section titled “Revoking Access”Remove a member_role row to revoke a role’s permissions. The change takes effect on the member’s next request (session is re-evaluated per request on the server).
DELETE FROM member_role WHERE member_id = '<member-id>' AND role_id = '<role-id>';