Risk Scores API¶
These endpoints expose identity risk scoring data and allow analysts to apply score adjustments with audit trails. In v5, scoring is driven from the UI (Admin > Risk Scoring) via the Node.js scoring engine. All endpoints require Authorization: Bearer <JWT>.
Risk Score Overview¶
The scoring engine assigns a numeric score (0–100) to every principal, resource, business role, context, and identity. Scores map to named tiers:
| Tier | Score Range | Color |
|---|---|---|
| Critical | 80–100 | Red |
| High | 60–79 | Orange |
| Medium | 40–59 | Yellow |
| Low | 20–39 | Blue |
| Minimal | 1–19 | Green |
| None | 0 | Grey |
Scoring runs in four layers:
- Direct match — classifier regex patterns matched against display names, job titles, and attributes
- Membership analysis — score contributions from high-risk resources the principal is a member of
- Structural hygiene — stale sign-ins, never-signed-in accounts, accounts with no expiration, orphaned identities
- Cross-entity propagation — risk flowing from high-risk principals upward to their contexts
Analyst overrides (+50 to −50) are preserved across re-scoring runs and applied after all four layers, with the final score clamped to 0–100.
Summary¶
GET /api/risk-scores¶
Overall risk score summary across all entity types. Used by the Risk Scoring page header and dashboard widgets.
Response
{
"summary": {
"totalEntities": 4210,
"scored": 4008,
"overrides": 23,
"lastScoredAt": "2026-03-27T04:05:00Z"
},
"tierDistribution": {
"Principal": {
"Critical": 12,
"High": 87,
"Medium": 341,
"Low": 1204,
"Minimal": 1809,
"None": 68
},
"Resource": {
"Critical": 4,
"High": 31,
"Medium": 128,
"Low": 249,
"Minimal": 0,
"None": 0
},
"BusinessRole": { ... },
"Context": { ... },
"Identity": { ... }
}
}
Reads From: RiskScores
Entity Type Lists¶
Each entity type has a dedicated paginated list endpoint.
| Endpoint | Entity Type | Reads From |
|---|---|---|
GET /api/risk-scores/users |
Principals | RiskScores + Principals |
GET /api/risk-scores/groups |
Resources (non-BusinessRole) | RiskScores + Resources |
GET /api/risk-scores/business-roles |
Resources (resourceType='BusinessRole') |
RiskScores + Resources |
GET /api/risk-scores/contexts |
Contexts | RiskScores + Contexts |
GET /api/risk-scores/identities |
Identities | RiskScores + Identities |
Common Query Parameters¶
All list endpoints accept the same parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
search |
string | Filter by displayName (SQL LIKE %term%) |
|
tier |
string | Filter by tier: Critical, High, Medium, Low, Minimal, None |
|
overridesOnly |
string | true = only entities that have an active analyst override |
|
limit |
int | 100 | Page size. Maximum: 500. |
offset |
int | 0 | Pagination offset. |
Response Format¶
{
"data": [
{
"entityId": "uuid",
"displayName": "Jane Doe",
"entityType": "Principal",
"score": 73,
"tier": "High",
"baseScore": 68,
"overrideAdjustment": 5,
"overrideReason": "Privileged project access — temporary elevation approved",
"overrideBy": "john.smith@contoso.com",
"overrideAt": "2026-03-15T11:00:00Z",
"topContributors": [
{ "factor": "DirectMatch", "weight": 35, "detail": "Classifier: Finance-Privileged" },
{ "factor": "MembershipRisk", "weight": 28, "detail": "Member of 3 Critical resources" },
{ "factor": "StaleSignIn", "weight": 10, "detail": "Last sign-in: 95 days ago" }
]
}
],
"total": 521
}
Response Fields
| Field | Type | Description |
|---|---|---|
score |
int | Effective score (0–100) = base components + override, clamped |
baseScore |
int | Score before any analyst override |
overrideAdjustment |
int | Analyst adjustment (−50 to +50). null if no override. |
overrideReason |
string | Required reasoning supplied by the analyst. null if no override. |
topContributors |
array | Top scoring factors with weights and descriptions |
Single Entity & Override¶
GET /api/risk-scores/:type/:id¶
Retrieve the current risk score for a single entity.
Path Parameters
| Parameter | Values |
|---|---|
type |
users, groups, business-roles, contexts, identities |
id |
Entity UUID |
Response: Same structure as a single item from the list endpoints above, plus full contributors array (not just top 3).
PUT /api/risk-scores/:type/:id/override¶
Apply an analyst score adjustment to a single entity. The adjustment is stored with the analyst's identity (from JWT upn claim) and required reasoning.
Path Parameters
| Parameter | Values |
|---|---|
type |
users, groups, business-roles, contexts, identities |
id |
Entity UUID |
Request Body
{
"adjustment": -20,
"reason": "False positive — display name matches classifier but account is a shared mailbox with no sensitive access"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
adjustment |
int | Yes | Integer from −50 to +50 |
reason |
string | Yes | Non-empty. Capped at 500 characters. |
Response
{
"entityId": "uuid",
"newScore": 48,
"baseScore": 68,
"overrideAdjustment": -20,
"overrideReason": "False positive — ...",
"overrideBy": "john.smith@contoso.com",
"overrideAt": "2026-03-27T14:22:00Z"
}
Override Persistence
Overrides are preserved when Invoke-FGRiskScoring re-runs. The engine writes the base score from its four-layer calculation; the override adjustment is applied on top when the API reads back the score.
Clearing an Override
To remove an override, set adjustment to 0 and supply a reason explaining the removal.
Resource Clusters¶
GET /api/risk-scores/clusters¶
List resource clusters created by Save-FGResourceClusters. Each cluster groups related resources by classifier or name-stem and has an aggregate risk score computed from its members.
Response
{
"data": [
{
"clusterId": "clust-001",
"displayName": "Finance Access Cluster",
"clusterType": "Classifier",
"ownerDisplayName": "Jane Doe",
"memberCount": 14,
"aggregateScore": 71,
"tier": "High",
"assignedBy": "john.smith@contoso.com"
}
]
}
Response Fields
| Field | Description |
|---|---|
clusterType |
Classifier (grouped by risk classifier) or NameStem (grouped by shared display name prefix) |
ownerDisplayName |
Analyst-assigned owner for remediation accountability |
aggregateScore |
Weighted average of member entity scores |
assignedBy |
Identity who assigned the cluster owner (derived from authenticated user, not request body) |
Reads From: ResourceClusters + ResourceClusterMembers + RiskScores
Org Chart¶
GET /api/org-chart¶
Manager hierarchy tree with risk scores propagated to department nodes. Used by the Org Chart page.
Caching: Response is cached for 5 minutes server-side. This avoids expensive recursive CTE execution on every page load.
Response
{
"tree": [
{
"principalId": "uuid",
"displayName": "Alice Johnson",
"jobTitle": "Chief Financial Officer",
"department": "Finance",
"riskScore": 42,
"riskTier": "Medium",
"directReportCount": 8,
"totalReportCount": 142,
"children": [
{
"principalId": "uuid",
"displayName": "Bob Chen",
"jobTitle": "Finance Director",
"riskScore": 67,
"riskTier": "High",
"directReportCount": 12,
"totalReportCount": 58,
"children": [ ... ]
}
]
}
],
"cachedAt": "2026-03-27T14:00:00Z"
}
Reads From: Principals (manager hierarchy via managerId self-join) + RiskScores + Contexts
Risk tiers on manager nodes reflect the highest risk tier among all direct and indirect reports — allowing managers to identify high-risk subtrees at a glance.