Appearance
Permissions
Hailer controls access to workspaces, workflows, activities, and features through a layered permission system. Understanding it is essential for building permission-aware integrations - and for predicting whether a call will succeed before you make it.
The layers
Permissions resolve through several interconnected layers:
- Workspace roles - base access level within a workspace (owner, admin, user, guest, inviter).
- Custom roles - granular permission sets built from specific permits.
- Workflow permissions - access to a workflow and its activities (admin, any, ownteam, own, none, guest).
- Phase permissions - access within a workflow phase (edit, view, none).
- Team membership - team-based access that intersects with the levels above.
Workspace roles
| Role | What it grants |
|---|---|
| Owner | Highest privilege. Can delete the workspace and manage billing. Cannot be removed while sole owner. |
| Admin | User and workspace management. Cannot delete the workspace or manage billing. Full access to workflows, insights, and apps unless restricted. |
| User | Standard member. Creates and manages own content; access to workflows/activities via teams and workflow settings. |
| Guest | Limited, often read-only access. Cannot reach most administrative features. Used for external collaborators. |
| Inviter | Can invite new users, with little other administrative privilege. |
Workflow permissions
Applied per workflow, controlling access to its activities:
| Level | Meaning |
|---|---|
| admin | Full control over the workflow and every activity; overrides team and ownership restrictions. |
| any | Access to all activities regardless of ownership (subject to phase permissions). |
| ownteam | Access limited to activities owned by teams the user belongs to. |
| own | Access only to activities the user created or owns. |
| none | No access; the workflow and its activities are hidden. |
| guest | Limited, usually read-only guest access. |
Phase permissions
Within a workflow, each phase refines access further:
| Level | Meaning |
|---|---|
| edit | Can modify activities in the phase and move them (if allowed). |
| view | Read-only: can see activities and discussions, cannot modify. |
| none | Activities in the phase are hidden. |
Custom role permits
Custom roles grant capabilities through named permits. Common ones:
- Feed -
feed.manage,feed.interact,feed.load - Discussion -
discussion.start.private.any,discussion.start.private.publicTeam,discussion.message.add,discussion.message.add.attachment,discussion.message.remove.own,discussion.message.remove.others,discussion.read.history,discussion.manage,discussion.leave - Teams and groups -
team.load,group.load,group.share,group.loadShared - Views -
view.calendar,view.activities,view.apps,view.people
Manage custom roles and their permits with v3.network.role.*.
How permissions resolve
For a given action, Hailer checks, in order:
- Workspace role - owner / admin / user / guest / inviter?
- Custom role permits - which specific permits does the user hold?
- Workflow access - admin / any / ownteam / own for the workflow?
- Phase permission - edit / view / none in the relevant phase?
- Team membership - is the user on a team that owns the activity (for
ownteam)? - Activity ownership - does the user own the activity (for
own)?
Higher roles inherit the capabilities of lower ones, and custom-role permits can grant access beyond the base role.
Checking permissions via the API
v3.activity.permissions returns a user's resolved permissions for a workspace:
javascript
const permissions = await client.request('v3.activity.permissions', [
userId, // user to check
workspaceId, // workspace context
{
details: true, // include detailed permission paths
items: { workflowIds: ['workflowId'] }, // optionally scope to specific resources
},
]);
const workspace = permissions[workspaceId];
// Workspace role
console.log('Is admin:', workspace.workspace.isAdmin);
console.log('Is owner:', workspace.workspace.isOwner);
// Workflow + phase permission
const workflow = workspace.workflow['workflowId'];
console.log('Workflow access:', workflow.hasAccess, workflow.permission);
console.log('Can edit phase:', workflow.phases['phaseId']?.permission === 'edit');Pre-flight checks
Check before you act, rather than catching a permission error after the fact:
javascript
async function canEditActivity(userId, workspaceId, workflowId, phaseId) {
const perms = await client.request('v3.activity.permissions', [userId, workspaceId,
{ items: { workflowIds: [workflowId] } }]);
const workflow = perms[workspaceId].workflow[workflowId];
return workflow?.hasAccess
&& ['admin', 'any', 'ownteam', 'own'].includes(workflow.permission)
&& workflow.phases?.[phaseId]?.permission === 'edit';
}Team-based (ownteam) access
javascript
async function canAccessTeamActivity(userId, workspaceId, activity) {
const perms = await client.request('v3.activity.permissions', [userId, workspaceId]);
const workflow = perms[workspaceId].workflow[activity.process];
if (['admin', 'any'].includes(workflow.permission)) return true;
if (workflow.permission === 'ownteam') return perms[workspaceId].teams.includes(activity.team_account?.team);
if (workflow.permission === 'own') return activity.uid === userId;
return false;
}Best practices
- Enforce on the server. Client-side checks improve UX but are never a security boundary - the backend enforces permissions on every call regardless.
- Cache briefly. Permission results are stable over short windows; caching for a few minutes cuts calls. Invalidate when roles or team memberships change.
- Scope your checks. Pass
itemstov3.activity.permissionsto resolve only the workflows or calendars you care about. - Least privilege. Request and grant only the access an integration actually needs.
Related endpoints
v3.activity.permissions- resolved user permissions.v2.core.init- current user and workspace bootstrap.v2.network.get- workspace details and member roles.v3.network.role.*- custom roles and their permits.v2.process.member.list- workflow-specific permissions.