Entity Detail API¶
These endpoints power the detail pages for users, resources, business roles, systems, org units, and identities. All endpoints require Authorization: Bearer <JWT>.
Users¶
Column Discovery¶
GET /api/user-columns-page¶
Column discovery for the Users page. Returns all populated columns from the Principals table with distinct values, plus the virtual __userTag column if any user tags exist. Used to build the filter UI on the Users page.
Same response format as GET /api/user-columns.
User List¶
GET /api/users¶
Paginated list of principals with their tags.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | Full-text search on displayName and userPrincipalName (SQL LIKE %term%) |
|
limit |
int | 100 | Page size. Maximum: 500. |
offset |
int | 0 | Pagination offset. |
filters |
JSON string | Attribute filters. Same format as Matrix filters. |
Response
{
"data": [
{
"id": "uuid",
"displayName": "Jane Doe",
"userPrincipalName": "jane.doe@contoso.com",
"department": "Finance",
"jobTitle": "Analyst",
"principalType": "User",
"tags": ["VIP", "External"]
}
],
"total": 3421
}
Reads From: Principals + GraphTagAssignments + GraphTags
User Detail¶
GET /api/user/:id¶
Single user attributes and counts (number of resource memberships, number of business role assignments).
Response
{
"id": "uuid",
"displayName": "Jane Doe",
"userPrincipalName": "jane.doe@contoso.com",
"department": "Finance",
"jobTitle": "Analyst",
"principalType": "User",
"extendedAttributes": {
"accountEnabled": true,
"lastSignInDateTime": "2026-03-20T08:14:00Z"
},
"membershipCount": 12,
"accessPackageCount": 3
}
Reads From: Principals
GET /api/user/:id/memberships¶
All resource memberships for a user, with membership type badges. Used by the Memberships section of the User detail page.
Response
{
"data": [
{
"groupId": "uuid",
"groupDisplayName": "SG-Finance-Base",
"membershipType": "Direct",
"managedByAccessPackage": true,
"accessPackageIds": ["ap-001"]
}
]
}
Reads From: mat_UserPermissionAssignments + Resources
GET /api/user/:id/access-packages¶
Business role (access package) assignments for a user. Only returns active governed assignments (state='Delivered').
Response
{
"data": [
{
"resourceId": "ap-001",
"displayName": "Finance Base Access",
"catalogDisplayName": "Corporate Catalog",
"assignmentStatus": "Delivered",
"assignedDateTime": "2026-01-10T09:00:00Z",
"expirationDateTime": null,
"policyId": "pol-001"
}
]
}
Reads From: ResourceAssignments (assignmentType='Governed') + Resources (resourceType='BusinessRole') + GovernanceCatalogs
GET /api/user/:id/history¶
Version history for a user principal. Each entry represents a recorded change from the _history audit table, including the operation type and a JSONB snapshot of the row data.
Response
{
"history": [
{
"changedAt": "2026-01-15T10:23:00Z",
"operation": "U",
"rowData": { "displayName": "Jane Doe", "department": "Finance" },
"prevData": { "displayName": "Jane Doe", "department": "HR" }
}
]
}
Reads From: _history audit table (filtered by tableName = 'Principals')
Resources¶
Column Discovery¶
GET /api/group-columns¶
Column discovery for the Groups/Resources page. Returns populated columns from the Resources table with distinct values, plus the virtual __groupTag column if any group tags exist.
Reads From: Resources (via db/columnCache.js, 5-minute TTL)
Resource List¶
GET /api/groups¶
Paginated resource list with tags. The legacy path used by the Groups page; identical behavior to /api/resources with resourceType pre-filtered to group-like types.
Query Parameters: Same as GET /api/users.
Reads From: Resources + GraphTagAssignments + GraphTags
GET /api/resources¶
Paginated resource list with optional type and system filters. Used by the Resources page.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | Search displayName (SQL LIKE) |
|
resourceType |
string | Filter by resourceType (e.g. Group, DirectoryRole, AppRole, BusinessRole) |
|
systemId |
string | Filter by originating system | |
limit |
int | 100 | Page size. Maximum: 500. |
offset |
int | 0 | Pagination offset. |
filters |
JSON string | Attribute filters. |
Reads From: Resources + GraphTagAssignments + GraphTags
Resource Detail¶
GET /api/resources/:id¶
Single resource attributes including the extendedAttributes JSON column, plus counts (members, business roles that grant access to this resource).
Response
{
"id": "uuid",
"displayName": "SG-Finance-Base",
"resourceType": "Group",
"systemId": "sys-001",
"systemDisplayName": "EntraID",
"extendedAttributes": {
"mailEnabled": false,
"securityEnabled": true,
"groupTypes": []
},
"memberCount": 47,
"accessPackageCount": 2,
"tags": ["Finance", "Baseline"]
}
Reads From: Resources
GET /api/resources/:id/members¶
Paginated list of principals assigned to this resource, with membership type badges.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
membershipType |
string | Filter by type: Direct, Indirect, Eligible, Owner |
|
limit |
int | 100 | Page size. Maximum: 500. |
offset |
int | 0 | Pagination offset. |
Reads From: mat_UserPermissionAssignments
GET /api/resources/:id/history¶
Version history for a resource. Same format as GET /api/user/:id/history.
Reads From: Resources via_historyaudit table
GET /api/group/:id/access-packages¶
Business roles that include this resource as a contained resource (i.e. granting membership or ownership of this group as part of a business role).
Response
{
"data": [
{
"resourceId": "ap-001",
"displayName": "Finance Base Access",
"roleName": "Member",
"catalogDisplayName": "Corporate Catalog"
}
]
}
Reads From: ResourceRelationships (relationshipType='Contains') + Resources (resourceType='BusinessRole') + GovernanceCatalogs
Business Role Detail¶
GET /api/access-package/:id¶
Core attributes for a business role, including assignment counts, catalog info, and review status.
Review status distinguishes two "no decisions yet" states:
| Review Status | Meaning |
|---|---|
"Not required" |
No review policy is configured on this role |
"Pending first review" |
A review policy exists but no review instance has run yet |
"Active" |
Review instance exists with decisions |
Reads From: Resources (resourceType='BusinessRole') + GovernanceCatalogs + AssignmentPolicies + CertificationDecisions
GET /api/access-package/:id/assignments¶
Active governed assignments for a business role. Only assignments with state='Delivered' are returned.
Response
{
"data": [
{
"principalId": "uuid",
"principalDisplayName": "Jane Doe",
"userPrincipalName": "jane.doe@contoso.com",
"assignedDateTime": "2026-01-10T09:00:00Z",
"expirationDateTime": null,
"policyId": "pol-001",
"assignmentStatus": "Delivered"
}
],
"total": 48
}
Reads From: ResourceAssignments (assignmentType='Governed') + Principals
GET /api/access-package/:id/resource-roles¶
Resources granted by this business role (the SOLL definition — what resources a user gets when this role is assigned).
Response
{
"data": [
{
"resourceId": "uuid",
"displayName": "SG-Finance-Base",
"resourceType": "Group",
"roleName": "Member",
"roleOriginSystem": "EntraID"
}
]
}
Reads From: ResourceRelationships (relationshipType='Contains') + Resources
GET /api/access-package/:id/policies¶
Assignment policies for a business role. Distinguishes auto-assigned (ABAC) policies from request-based (self-service) policies.
Response
{
"data": [
{
"policyId": "pol-001",
"displayName": "All Employees",
"policyType": "AutoAssignment",
"requestorScope": "AllMemberUsers",
"policyConditions": {
"department": "Finance"
}
}
]
}
Reads From: AssignmentPolicies
GET /api/access-package/:id/reviews¶
Certification decisions for this business role.
Response
{
"data": [
{
"reviewId": "rev-001",
"principalDisplayName": "Jane Doe",
"reviewerDisplayName": "John Smith",
"decision": "Approve",
"reviewedDateTime": "2026-03-01T14:30:00Z",
"isAutoReview": false,
"certificationScopeType": "BusinessRole"
}
]
}
isAutoReview: true indicates an AAD Access Review auto-applied decision (no human reviewer).
Reads From: CertificationDecisions
GET /api/access-package/:id/requests¶
Pending (open) assignment requests for a business role.
Reads From: AssignmentRequests + Principals
GET /api/access-package/:id/history¶
Version history for the business role resource record. Same format as GET /api/user/:id/history.
Reads From: Resources via_historyaudit table
Systems & Contexts¶
Systems¶
| Endpoint | Method | Description |
|---|---|---|
/api/systems |
GET |
All connected systems with resource counts, assignment counts, and resource type breakdown |
/api/systems/:id |
GET |
Single system detail |
/api/systems/:id |
PUT |
Update system metadata. Body: { displayName?, description?, enabled? } |
/api/systems/:id/owners |
GET |
List system owners (principals assigned as responsible for this system) |
/api/systems/:id/owners |
POST |
Add system owner. Body: { principalId } |
/api/systems/:id/owners/:userId |
DELETE |
Remove a system owner |
GET /api/systems response example:
{
"data": [
{
"systemId": "sys-001",
"displayName": "EntraID",
"enabled": true,
"resourceCount": 412,
"assignmentCount": 8903,
"resourceTypes": ["Group", "DirectoryRole", "AppRole"]
}
]
}
Reads From: Systems + Resources + ResourceAssignments
Contexts¶
Contexts represent organizational and structural groupings (departments, divisions, cost centers, teams, etc.) that belong to Identities — the real persons — not to individual system accounts. The contextType column discriminates between context types.
| Endpoint | Method | Description |
|---|---|---|
/api/contexts |
GET |
Flat list of contexts with hierarchy pointers |
/api/contexts/tree |
GET |
Pre-built nested tree structure for the Org Chart page |
/api/contexts/:id |
GET |
Context detail with member count and sub-context list |
/api/contexts/:id/members |
GET |
Paginated list of identities (and their correlated principals) in this context |
GET /api/contexts/tree response excerpt:
{
"tree": [
{
"id": "ctx-001",
"displayName": "Finance",
"contextType": "Department",
"memberCount": 142,
"children": [
{
"id": "ctx-007",
"displayName": "Finance - Controllers",
"contextType": "Team",
"memberCount": 23,
"children": []
}
]
}
]
}
Reads From: Contexts + Identities + IdentityMembers
Identity Correlation¶
GET /api/identities¶
Paginated list of correlated identities — real persons aggregated from multiple accounts across systems.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | Search by display name | |
limit |
int | 100 | Page size. Maximum: 500. |
offset |
int | 0 | Pagination offset. |
Response
{
"data": [
{
"identityId": "iid-001",
"displayName": "Jane Doe",
"accounts": [
{ "principalId": "uuid", "systemDisplayName": "EntraID", "principalType": "User" },
{ "principalId": "uuid", "systemDisplayName": "GitHub", "principalType": "ServicePrincipal" }
],
"verifiedAt": null,
"verifiedBy": null
}
],
"total": 1280
}
Reads From: Identities + IdentityMembers + Principals + Systems
PUT /api/identities/:id/verify¶
Analyst verification that the correlation result is correct (or mark it as incorrect for manual remediation).
Request Body
notes is capped at 2000 characters.
User Preferences¶
Preferences are stored per-user in the GraphUserPreferences SQL table (auto-created on first access). The user is identified by the Entra ID oid claim from their JWT. In no-auth mode, the key anonymous is used as a fallback.
GET /api/preferences¶
Returns the current user's tab visibility preferences.
Response
Optional tabs that can be toggled: risk-scores, identities, org-chart, performance. All are hidden by default until the user enables them via the settings dropdown (user avatar, top right).
PUT /api/preferences¶
Update the current user's tab visibility.
Request Body
Response: 204 No Content
Operations¶
Performance Metrics¶
Performance monitoring is opt-in. Enable by setting PERF_METRICS_ENABLED=true.
| Endpoint | Method | Description |
|---|---|---|
/api/perf |
GET |
Aggregated endpoint summaries — P50, P95, P99 latency per route |
/api/perf/recent |
GET |
Last N requests with per-SQL-query timing breakdowns |
/api/perf/slowest |
GET |
Slowest N requests (by total duration) |
/api/perf/export |
GET |
Download full ring buffer as JSON for offline analysis |
/api/perf/clear |
POST |
Clear the ring buffer (admin use) |
The ring buffer holds 1000 entries. When disabled (PERF_METRICS_ENABLED is unset or false), no overhead is incurred — the middleware is a no-op.
Server-Timing response headers are emitted on every request when enabled, making per-request SQL timing visible in browser DevTools (Network tab → Timing).