Eviworx
Docs

Users, Roles & Agent Groups API

This API covers user management, dynamic role-based access control (RBAC), the manager hierarchy (org chart), agents incl. agent groups with assignment strategies, and specialties (skill routing). The auth/permission model itself is documented centrally under Permissions & RBAC.

Absences/substitution (with manager approval) are a separate domain: Absences API. Permission resolution, cache & critical actions: Permissions & RBAC.

👥
Features
✓ Dynamic roles (own permission set + priority)
✓ Manager hierarchy & org chart
✓ Creation via invitation
✓ Archiving with handover to a successor
✓ Agent groups (4 assignment strategies)
✓ Specialties for skill routing (SKILL_BASED)
✓ Email-only contacts (no portal login)
✓ EntraID/SSO (group → role)

Pre-installed Roles

Besides the system roles, any number of custom roles can be created, each with its own permission set and a priority (order/precedence). System roles (isSystem) can neither be deactivated, deleted nor moved. Pre-installed are the four system roles and the DATA_PROTECTION_OFFICER role, which is created as a custom role:

RolePurpose
END_USERCustomer/requester — own tickets
AGENTSupport agent — manage all tickets/problems
ADMINAdministrator — full access
APPROVERChange approver
DATA_PROTECTION_OFFICERData protection officer (GDPR: breach details, incident view, audit); custom role, isSystem = false

User Endpoints /api/users

MethodEndpointDescription
GET/List (filter/search); anonymized users hidden by default — includeAnonymized=true for administration only
GET/statsCounts per role
GET/assignable?entityType=Assignable candidates per entity kind (TICKET, PROBLEM, CHANGE, INCIDENT, WORKFLOW) — with ?groupId, ?search, ?take/skip
GET/approvers?entityType=Users with approve permission (CHANGE, CHANGE_TEMPLATE, INCIDENT)
GET/checkout-recipientsRecipients for asset checkout
GET/check-emailCheck email availability
GET/:idSingle user
POST/Create user (with invitation); an invitation requires an active account — otherwise 400 INVITATION_ACCOUNT_NOT_ACTIVE
PATCH/:idUpdate user (incl. roleId)
PATCH/:id/profileProfile/status (isActive …)
POST/:id/promotePromote an email contact to a user (portal access, optionally a new role); requires an active account — otherwise 400 PROMOTE_ACCOUNT_NOT_ACTIVE
POST/:id/resend-invitationResend invitation; requires an active account — otherwise 400 INVITATION_ACCOUNT_NOT_ACTIVE
GET / PUT/:id/languageLanguage (de, en, es, fr, it)
GET/:id/archival-checkCheck if archivable
POST/:id/archive-with-transferArchive + transfer items

A user's display preferences: Language, timezone and date format belong to the user — the language via /:id/language, timezone and date format via their own profile (PUT /api/auth/profile). They apply in the interface and equally in the texts the server generates: email, push, Teams and Webex. A message therefore reaches the recipient in their language, with their times and in their date format, regardless of who triggered it. Which level applies when nothing is set.

Note: there is no DELETE endpoint for users. Deactivate via /:id/profile (isActive=false) or archive via /:id/archive-with-transfer (items go to a successor).

Three account states: An account is active, blocked or archived — archived ALWAYS implies blocked. Sending only isArchived: true adds isActive: false silently; asking for isActive: true on an archived account returns 400 ARCHIVED_USER_CANNOT_BE_ACTIVE. Reactivation sends both fields in ONE request (isArchived: false, isActive: true). This way an archived account drops out wherever only active accounts count: notifications, workflows and directory sync. Blocked and archived accounts cannot sign in on any path (403 ACCOUNT_DEACTIVATED); a sign-in never changes the status, unarchiving is only possible via PATCH /:id/profile with users.archive.

A blocked or archived account cannot become the customer of a new ticket, nor the successor when archiving (400 SUCCESSOR_NOT_ACTIVE). An incoming email to an archived account does NOT change its status — it only moves the erasure deadline, because a status change is an administrative decision: it requires a permission, is audited and can transfer manager relationships.

A blocked account names the origin of its block: managedProfile.lockSource is ADMIN (administration), ENTRA_SYNC (directory sync) or SYSTEM. On an active account the field is null. It answers the question who can lift the block again — the directory sync only lifts its own blocks.

A status change takes effect immediately: blocking an account ends all of its access — running sessions, refresh tokens, push subscriptions and open real-time connections; the permission cache is cleared. This holds for every path that writes the status: creation, editing, profile, archiving with a successor, directory sync and anonymization. Invitation and promotion therefore require an active account (400 INVITATION_ACCOUNT_NOT_ACTIVE resp. PROMOTE_ACCOUNT_NOT_ACTIVE) — a setup link sent to a blocked account would lead nowhere.

Protected accounts: The platform keeps two accounts for itself: the internal system account under which automated operations run, and the local emergency access through which administration gets in even if the Microsoft sign-in fails. Neither can be blocked, archived, anonymized or put under an erasure hold; their role is fixed. Such operations answer 403 PROTECTED_ACCOUNT, and details.operation names the rejected one (status, archive, role or erase). The directory sync never links either account (conflict code PROTECTED_ACCOUNT), and the system account does not sign in on any path. The UI marks these accounts and does not even offer the blocked actions. Profile data, password and two-factor sign-in of the emergency access stay editable.

Role assignment on an account: A roleId is set by POST /, PATCH /:id and /:id/promote — under three limits. The role of a synchronized account belongs to the directory: a role change is rejected with 403 ROLE_MANAGED_BY_ENTRA_ID as long as the account sits in a mapped role group (with entraIDConflict = true it stays manually settable, because the sync does not overwrite it there). A role without usable permissions — deactivated or with an invalid permission matrix — cannot be assigned: 400 ROLE_NOT_ASSIGNABLE with details.roleName, because the account would afterwards be rejected everywhere without being blocked. And nobody assigns a role with higher privileges than their own (403).

Visibility before action: every route targeting a specific user (PATCH /:id, /:id/profile, /:id/language, /:id/promote, /:id/resend-invitation, /:id/archival-check, /:id/archive-with-transfer, the manager routes, the hierarchy reads) first checks whether the caller may see that user at all — their own account or users.viewAll. A role holding users.edit or users.archive without users.viewAll therefore only affects its own profile; anything else is 403.

Archiving requires users.archive; users.manageManagers is only needed when manager relationships are actually transferred. users.archive and agents.manageGroups are critical actions: their permissions are revalidated straight from the database and both successes and denied attempts are audited.

Candidate lists require a permission on the target domain: /assignable requires a permission on the TARGET domain — viewAll, editAll or assign of the respective entity kind (for WORKFLOW: editTemplates, createTemplates or reassignSteps). editOwn deliberately does NOT suffice, otherwise any end user could query the agent list. /approvers requires the approval permission of the entity kind (e.g. changes.approve, incidents.approveClosure) or editAll/editOwn. Roles without those permissions receive 403.

Manager Hierarchy

MethodEndpointDescription
GET/:id/managersManagers of a user
POST/:id/managersAssign manager
PATCH/:id/managers/:managerIdUpdate relationship
DELETE/:id/managers/:managerIdRemove manager
GET/:id/subordinatesDirect subordinates
GET/:id/hierarchyFull org chart (recursive)

The manager hierarchy is the basis for escalations (assignee→lead→manager) and absence approvals.

Create User

POST /api/users
{
  "email": "agent@company.com",
  "name": "John Support",
  "roleId": "clx-role-agent",
  "managedProfile": { "firstName": "John", "lastName": "Support", "department": "IT", "location": "Munich" }
  // without password → invitation flow (password setup via link); see Authentication
}

Email-only contacts (autoCreatedFromEmail/emailOnlyContact) are created automatically on inbound email (sender policy AUTO_CREATE) and have no portal login — see Integrations.

Roles (RBAC) /api/roles

MethodEndpointDescription
GET/All roles (with permission matrix; settings.manageRoles). page/limit are validated: limit at most 200, unusable values are 400
GET/assignableLean assignment list for the user editor (id/name/displayName/color); requires users.manageRoles; contains only active roles the caller may assign (privilege ceiling)
GET/:idRole (with permission matrix)
POST/Create custom role
PATCH/:idUpdate permissions
DELETE/:idDelete custom role → 204. Not possible for system roles, for roles with users, and for roles carrying an ACTIVE API key (the key would otherwise be left without a role and rejected with 403 API_KEY_NO_ROLE).
GET/permissions/schemaPermission schema (for UI)
POST/:id/moveMove a role in the priority order — give EXACTLY one side (afterRoleId OR beforeRoleId); both or neither is 400

Error codes of role management

errorCodeHTTPMeaning
ROLE_NOT_FOUND404No role with this id
ROLE_NAME_EXISTS409The name is already taken
ROLE_IN_USE409The role is still held by users or active API keys — details.users and details.activeApiKeys give the counts
SYSTEM_ROLE_PROTECTED400System roles can neither be deactivated nor deleted nor moved
ROLE_PERMISSIONS_INVALID400The matrix contains unknown modules or actions, or non-boolean values
ROLE_LOCKOUT403settings.manageRoles must not be removed from a system role, your own role, or the LAST role that holds it — otherwise role management would be locked out permanently. details.reason names the case.
ROLE_NOT_ASSIGNABLE403Privilege ceiling: nobody assigns a role carrying rights they do not hold themselves. details.reason distinguishes ROLE_NOT_FOUND from PERMISSION_NOT_HELD (with details.permission).

Deactivating a role ends the access of its users: Switching isActive to false makes every account on that role lose its running sessions, refresh tokens, push subscriptions and open real-time connections at once; a new sign-in answers 403 NO_USABLE_ROLE. The accounts themselves stay active — the cause lies with the role, and the message says so. The UI therefore asks for confirmation as soon as the role still holds users; it names their number and points out that those affected receive no notification about it.

A role carries a permission matrix across 29 modules (tickets, problems, changes, incidents, assets, inventory, contracts, licenses, costCenters, knowledgeBase, elibrary, workflows, cronjobs, settings, users, agents, notifications, audit, analytics, absences, customReports, savedViews, …). How permissions are resolved, cached (per role) and freshly revalidated for critical actions is documented centrally under Permissions & RBAC.

POST /api/roles
{
  "name": "level2-agent",
  "displayName": "Level 2 Agent",
  "description": "Agent with change approval",
  "priority": 45000,
  "permissions": {
    "tickets":  { "viewAll": true, "create": true, "editAll": true, "assign": true },
    "changes":  { "viewAll": true, "approve": true, "reject": true }
    // more modules …
  }
}

Agents /api/agents

The agent profile augments a user with support-specific data (workload, isActive, specialties).

MethodEndpointDescription
GET/api/agentsList agents ({data, pagination}); ?forEntityType=TICKET|PROBLEM|CHANGE|INCIDENT|WORKFLOW narrows to the assignable ones
POST/api/agentsCreate agent profile ({userId, isActive})
PATCH/api/agents/:userIdUpdate agent: isActive and/or maxWorkload
DELETE/api/agents/:userIdRemove agent profile

Agents are addressed by userId everywhere: in paths, inputs and responses. Inputs are validated strictly; unknown fields and unknown query values (e.g. an invalid forEntityType) return 400.

Agent Groups

Groups route work to agents. applicableEntityTypes (array) defines which entity kinds the group serves as a queue for, assignmentStrategy decides the pick. Group access can restrict per group who sees it / can be assigned.

AspectValues
applicableEntityTypes (String[])TICKET, PROBLEM, CHANGE, INCIDENT, WORKFLOW (default: all five)
assignmentStrategyFIRST_AVAILABLE, ROUND_ROBIN, LEAST_LOADED, SKILL_BASED
MethodEndpoint
GET/api/agents/groups · /groups/:id
POST / PATCH/api/agents/groups · /groups/:id
POST/api/agents/groups/:id/archive · /restore
GET/api/agents/groups/:id/potential-members (server-side search: ?search=, ?take/skip, ?excludeAbsent, ?excludeInactive)
POST / DELETE / PATCH/api/agents/groups/:groupId/members/:userId (PATCH: isTeamLead and/or isActive → 204)
GET / POST/api/agents/groups/:id/access (access rules)
DELETE/api/agents/groups/:id/access/:accessId
PUT/api/agents/groups/:id/restriction (toggle restriction)
  • By default the group list returns only active, non-archived groups (for selection lists). Administration passes ?includeInactive=true to also get inactive and archived ones; the single lookup /groups/:id resolves inactive groups too, so existing assignments stay visible. Further query params: ?forEntityType=, ?search=.
  • Permissions: read groups = users.viewAgents · manage groups/members/specialties = agents.manageGroups · access rules = agents.manageGroupAccess. Every group response carries canManageMembers for the requesting user (agents.manageGroups or active team lead of that group) — the UI member actions follow it.
  • An access entry belongs to exactly one group: a second grant for the same user or role is 409 ACCESS_ALREADY_GRANTED, and a DELETE through a foreign group URL is 404.

Specialties /api/agents/specialties

Skills for SKILL_BASED routing. CRUD + assignment to agents:

MethodEndpoint
GET/ (?includeInactive=true for administration) · /:id · /agent/:userId
POST / PATCH / DELETE/ · /:id
POST / DELETE/:id/agents/:userId (attach/detach skill, proficiency 1–5)

Absence & Availability

Assignability accounts for absences: the candidate lists flag absent agents (isAbsent; the absence reason is not returned because it may contain health data), and if a target agent is absent, the substitute redirect applies at assignment time (override via ignoreSubstitution). Deactivated and archived accounts are never candidates. Absence management (incl. manager approval) on its own page: Absences API.

👥
Core Principles
  • ✓ Freely definable roles with priority
  • ✓ Manager hierarchy & org chart
  • ✓ Agent groups: queue, assignment strategy, access rules
  • ✓ Archiving with transfer to a successor
🔐
Permissions (RBAC)
  • users.viewAll / create / edit / manageRoles
  • settings.manageRoles – roles/permissions
  • agents.* – agents & groups

Auth/role model: Permissions & RBAC

Related Documentation