Skip to content

List segregation-of-duties violations

GET
/sod/violations
curl --request GET \
--url 'https://api.vendorica.com/v1/sod/violations?page=1&limit=100&sortBy=detectedAt&sortOrder=asc&subjectType=role' \
--header 'Authorization: Bearer <token>'

The violation register: every case where one holder was found with both halves of a duty pair the organization keeps separate. Filter with status, ruleId, subjectType and subjectId; page with page + limit. Findings are never deleted and a decision is terminal, so a consumer can poll on detectedAt and re-read a row to pick up its decision. Push delivery of the same finding is the sod.violation_detected webhook.

page
integer
default: 1 >= 1 <= 10000
limit
integer
default: 100 >= 1 <= 500
sortBy
string
Allowed values: detectedAt status
sortOrder
string
default: desc
Allowed values: asc desc
status
Array<string> | null
<= 100 items
Allowed values: open accepted resolved
ruleId
string format: uuid
subjectType
string
Allowed values: role user
subjectId
string format: uuid

Success

Media typeapplication/json
object
success
required
boolean
data
required
Array<object>
object
id
required
string format: uuid
ruleId
required
string format: uuid
ruleDisplayId
required

The rule this finding cites, as shown in the app (SOD-1). Null only if the rule has since been removed — the finding outlives it.

string | null
subjectType
required

What holds the conflict: role (one role concentrates both halves of a duty pair) or user (one person performed both halves on one record). Typed as a string rather than a closed enum because the set grows with the evaluation points; treat an unrecognised value as neither.

string
subjectId
required
string format: uuid
subjectLabel
required

The role or person the finding is about, resolved at read time. Null when that subject no longer exists.

string | null
atomA
required

One half of what collided, as a tagged atom — permission:<name> for a role finding, activity:<key> for a four-eyes one.

string
atomB
required
string
evidence
required

What the evaluator saw, keyed by kind (role_grant or four_eyes). Carries the duty names, everything each duty matched, and enforcementAtDetection — the rung the rule was on WHEN THE FINDING WAS MADE. Correlate on that rather than on the rule’s rung today: a rule promoted since is a different statement about the same finding.

object
key
additional properties
detectedAt
required
string format: date-time
status
required

open awaits a decision. accepted and resolved are both terminal and neither returns to open — a conflict that recurs is filed as a NEW violation, because “it came back” and “it was never fixed” are different findings.

string
Allowed values: open accepted resolved
resolvedAt
required
string | null format: date-time
resolvedBy
required
string | null format: uuid
resolutionNote
required

Why the decision was made. Mandatory when one is recorded, so this is non-null on every decided row.

string | null
key
additional properties
timestamp
required
string format: date-time
requestId
string
message
string
pagination
object
page
required
integer
limit
required
integer
total
required
integer
totalPages
required
integer
Example
{
"success": true,
"data": [
{
"id": "26c9e168-79cd-49de-8c0d-f6e78cfba759",
"ruleId": "ebb7b1b2-c22c-4810-8f65-c5e93a369417",
"ruleDisplayId": "SOD-1",
"subjectType": "role",
"subjectId": "5bee1a50-09d3-4f16-8cd7-de0fc2d28b51",
"subjectLabel": "string",
"atomA": "permission:vendors:write",
"atomB": "string",
"evidence": {},
"detectedAt": "2026-08-19T09:41:12.004Z",
"status": "open",
"resolvedAt": "2026-08-19T09:41:12.004Z",
"resolvedBy": "55571c38-9221-4f4e-87a2-3637275b5249",
"resolutionNote": "string"
}
],
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H",
"message": "string",
"pagination": {
"page": 1,
"limit": 1,
"total": 1,
"totalPages": 1
}
}

Validation error

Media typeapplication/json
object
success
required
boolean
error
required
string
code
required
string
details
timestamp
required
string format: date-time
requestId
string
Example
{
"success": false,
"error": "Bad request",
"code": "BAD_REQUEST",
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H"
}

Unauthorized

Media typeapplication/json
object
success
required
boolean
error
required
string
code
required
string
details
timestamp
required
string format: date-time
requestId
string
Example
{
"success": false,
"error": "Unauthorized",
"code": "UNAUTHORIZED",
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H"
}

Forbidden — missing scope

Media typeapplication/json
object
success
required
boolean
error
required
string
code
required
string
details
timestamp
required
string format: date-time
requestId
string
Example
{
"success": false,
"error": "Forbidden",
"code": "FORBIDDEN",
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H"
}

Not found

Media typeapplication/json
object
success
required
boolean
error
required
string
code
required
string
details
timestamp
required
string format: date-time
requestId
string
Example
{
"success": false,
"error": "Resource not found",
"code": "NOT_FOUND",
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H"
}

Rate limit exceeded

Media typeapplication/json
object
success
required
boolean
error
required
string
code
required
string
details
timestamp
required
string format: date-time
requestId
string
Example
{
"success": false,
"error": "Too many requests",
"code": "TOO_MANY_REQUESTS",
"timestamp": "2026-08-19T09:41:12.004Z",
"requestId": "req_01J5X8ZC3K4T7Q9M2W6B0N1V4H"
}