Skip to content

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).

1. Create a role record with a permissions JSON array
2. Assign the role to a member via member_role
3. At login, get_auth_state_service loads the permissions into Session
4. Server: Resource::has_instance_permission / has_collection_permission enforces access
5. Client: ClientSession.has_permission() controls what the sidebar and buttons show

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.

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.

After the member logs in (or their session refreshes), Session.permissions contains the loaded permissions. Here’s what the “Agent Manager” example grants:

EndpointAccess
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
Nav linkVisible
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

Can view calls and contacts; cannot modify anything.

'["Call:Collection:List", "Call:Instance:View", "Contact:Collection:List", "Contact:Instance:View"]'::jsonb

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"
]'::jsonb

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"
]'::jsonb

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"]'::jsonb

With 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.

WhoServer behaviorUI behavior
Super-adminBypasses all checks, cross-orghas_permission() always true
Org ownerBypasses all checks within orghas_permission() always true
Member with rolesABAC on every endpointOnly sees what their permissions allow
Member with no roles403 on all gated endpointsSees only always-visible links

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>';