BetterIAM
Server API

threats

Identity threat detection and response: attacks found in the audit trail, incidents, identity risk, and responses.

Identity threat detection and response: attacks found in the audit trail, incidents, identity risk, and responses. Detection rules find them, incidents group them for investigation, a risk score per identity feeds policies, and administrators respond by hand or with playbooks. Password sprays, guessing, stolen sessions, replayed tokens, account takeover, self-granted administrator access, mass deletions, weakened security settings, and audit tampering all leave audit events; the iam.detectThreats() job reads them, raises detections, and groups them into one incident per subject. The repository guide is docs/threat-detection.md.

How detection works

Detection is a scheduled job, iam.detectThreats() (CLI detect-threats), not a hook in the request path. For each active tenant it reads the audit events recorded since its last run from a per-tenant cursor, verifies their hash chain (a break is an audit-tampering detection), evaluates the tenant's rules over them and the history the rules' windows need, and records what it finds. Detection is therefore as late as the job's interval and never slows a sign-in, and it sees every writer's events: the authentication service, SCIM, protocols, and deployment jobs.

  • Rules. Twenty-one built-in rules, each with a severity, a subject type, a MITRE ATT&CK technique, and (for counting rules) a threshold and window a tenant can tune within bounds. rules lists them with the setting in force.
  • Detections are recorded once each: a rule's unique key is its triggering event or its burst, so re-reading events never raises a second one. Each carries evidence: up to 20 audit event ids with counts, the time span, and the networks, identities, and actions involved.
  • Incidents group detections by subject (an identity, a network, the tenant, or a SCIM connection). A subject has at most one open or investigating incident; resolving it closes it, and the next detection opens a new one.
  • Subjects and networks. Networks are client addresses reduced to an IPv4 address or an IPv6 /64. Addresses are known only when the deployment records them (http.clientInfo). Networks in the tenant's trustedNetworks are never reported and never blocked.
  • Actors. Detections, incidents, and playbook responses are recorded by the actor threat-detection; everything done through this group by the person who did it.

Identity risk

A detection about an identity adds points by severity (low 10, medium 25, high 50, critical 80) that halve every riskHalfLifeHours (24 by default). The score is what remains, at most 100; the level is none below 10, low from 10, medium from 40, and high from 70. Scores are computed when read, so they decay without a writer. An administrator can set a floor with setRisk; dismissing a detection, or resolving its incident as a false positive or benign, takes its points out. Nobody clears risk raised about themselves: detections about you are dismissed, and your own false-positive incidents closed, by someone else (root administrators excepted), and nobody sets their own level.

Policies read the effective values as principal.riskLevel and principal.riskScore (see principal keys). A delegated session carries the higher of the person's and the agent's risk; simulated principals (invariants, impact previews, simulation, and package-rule automation) always see none and 0.

Responses and protections

The five response actions are revoke-sessions (API keys kept unless keepApiKeys: false), forget-devices, contain (the identity is disabled until release; its API keys are kept but refused meanwhile), block-network (a tenant network block for durationMs, one minute to 30 days, one day by default), and notify (the threat-alert email to the tenant's notify recipients). People take them with respond; playbooks take them for new detections that match their trigger.

  • People cannot contain themselves or the last owner, contain an owner unless they are an owner of the tenant in person or root, end a root administrator's sessions or contain one unless they are root, block a network that covers their own address, or block a network on the root tenant unless they are root.
  • Playbooks never contain owners or root administrators and never block networks on the root tenant (skipped as protected), contain at most maxAutomaticContainments identities per tenant and run (skipped as braked beyond it, audited once as threat:response-braked), and email an incident again only when its severity rose.
  • Nobody blocks a trusted network (skipped as trusted-network).

Every action taken or skipped is kept as a response on the incident's timeline. Shared Signals receivers get threat:revoke-sessions as CAEP session-revoked and threat:contain as RISC account-disabled.

Who may call what

Reading needs iam:threats:read, tuning and triage iam:threats:manage, and responding iam:threats:respond. They are checked on iam/threats/rules, iam/threats/settings, iam/threats/detections, iam/threats/incidents, iam/threats/risk, iam/threats/playbooks (with /{id} for one record), and iam/threats/responses. Every call is audited under its permission (reads included), and changes also record the threat:* event each method names. reportSuspicious needs no permission: it acts on the caller's own account only.

Methods24
Serveriam.api.threats
Clientclient.threats
HTTPPOST /api/iam/threats/*
MethodWhat it doesAccess
addNoteAdds an investigation note to an incident.Credential
configureTunes detection for the tenant: rules, trusted networks, dormancy, risk decay, alert recipients, and the containment brake.Credential
createPlaybookDefines an automatic response: actions that run when a new detection matches the trigger.Credential
deletePlaybookDeletes a playbook; responses it already took stay on their incidents.Credential
detectRuns detection for the tenant now instead of waiting for the scheduled job, and returns what the run did.Credential
dismissDetectionMarks an open detection as a false alarm and takes its points out of the identity's risk.Credential
getDetectionReturns one detection with its evidence.Credential
getIncidentReturns an incident with its detections, notes, responses, and the risk of the identity it concerns.Credential
getRiskReturns one identity's risk: score, level, override, containment, and the contributions behind them.Credential
getSettingsReturns the tenant's detection settings with every default applied.Credential
listDetectionsLists the tenant's detections, newest first, optionally narrowed by status, rule, severity, identity, incident, and time.Credential
listIncidentsLists the tenant's incidents, most recently active first, optionally narrowed by status, severity, assignee, and identity.Credential
listPlaybooksLists the tenant's playbooks by name.Credential
listRiskLists the identities at or above a risk level, and contained ones, highest score first.Credential
releaseLifts a containment, so the identity is active again and its API keys work.Credential
reportSuspiciousLets a person report activity on their own account that was not them: it raises an incident and signs out everywhere else.Credential
resolveIncidentCloses an incident as a true positive, a false positive, or benign.Credential
respondResponds by hand to an incident, an identity, or a network with one to five response actions.Credential
rulesLists every detection rule with its defaults, tuning bounds, and the setting in force for the tenant.Credential
setRiskSets an identity's risk level by hand, as a floor that holds until it expires, or clears it.Credential
summaryReturns the tenant's threat posture at a glance for a dashboard.Credential
timelineReturns an identity's recent audit trail, newest first: what it did and what was done to it.Credential
updateIncidentMoves an incident between open and investigating, assigns it, or changes its severity.Credential
updatePlaybookChanges a playbook's name, description, trigger, actions, or whether it is enabled.Credential

addNote

Adds an investigation note to an incident.

POST/api/iam/threats/addNote
client.threats.addNote()Credential

Used inThreat detection

  • Permission: iam:threats:manage on iam/threats/incidents/{id}.
  • Audited as: iam:threats:manage and threat:note (metadata: noteId, length; never the text).
  • Errors: INVALID_INPUT for a body that is empty or over 4000 characters; LIMIT_EXCEEDED (409) past 500 notes on the incident; NOT_FOUND when the incident is not in this tenant.

Notes may span several lines (line breaks are normalized, other control characters dropped) and can be added to resolved incidents too. No recent sign-in is needed, so an investigator can keep writing during a long incident. getIncident returns them oldest first.

Input

Prop

Type

Returns

A ThreatNote object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/addNote" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "incidentId": "<incidentId>",
  "body": "<body>"
}'
Signature
iam.api.threats.addNote(
  credential: CredentialInput,
  input: { tenantId: string; incidentId: string; body: string },
): Promise<ThreatNote>

configure

Tunes detection for the tenant: rules, trusted networks, dormancy, risk decay, alert recipients, and the containment brake.

POST/api/iam/threats/configure
client.threats.configure()Credential

Used inThreat detection

  • Permission: iam:threats:manage on iam/threats/settings, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:settings (metadata: changed, changedRules, disabledRules, weakened); turning a rule off or weakening detection otherwise also records a guardrail-weakened detection against the caller.
  • Errors: ACCESS_DENIED for shortening riskHalfLifeHours while detections raise the caller's own risk (root administrators excepted); INVALID_INPUT for an unknown rule or setting, a threshold or windowMs outside the rule's tunable bounds (or for a rule that has none), everyone on a rule other than new-network, more than 50 trusted networks or one that is not an address or CIDR block or is wider than /8 (IPv4) or /32 (IPv6), dormantDays outside 7 to 3650, riskHalfLifeHours outside 1 to 720, more than 20 or malformed notify.emails, or maxAutomaticContainments outside 0 to 100; RECENT_AUTH_REQUIRED.

Fields left out keep their value. rules is merged per rule: each rule takes enabled, severity, threshold, windowMs, and (for new-network) everyone, where null restores that field's default and null for the whole rule drops every adjustment. trustedNetworks replaces the list. notify names who the notify response emails: owners (active owners with a verified email) and up to 20 emails. The result is the settings with every default applied, as getSettings returns them. Because the detection engine never reads the module's own events, weakening detection is reported here, as a guardrail-weakened detection that runs the tenant's playbooks: turning a rule off; a higher threshold, a shorter window, or a lower severity on a rule that stays on; new-network no longer reporting everyone; a shorter risk half-life; a longer dormancy period; a newly trusted network; a lower maxAutomaticContainments; or fewer incident recipients. Its metadata lists them as weakened (for example rules.brute-force.threshold, trustedNetworks).

await iam.api.threats.configure(credential, {
  tenantId,
  rules: { 'brute-force': { threshold: 20 }, 'new-network': { everyone: true } },
  trustedNetworks: ['203.0.113.0/24'],
  notify: { owners: true, emails: ['soc@acme.test'] },
});
Input

Prop

Type

Returns

A ThreatSettingsView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/configure" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.configure(
  credential: CredentialInput,
  input: ThreatSettingsInput & { tenantId: string },
): Promise<ThreatSettingsView>

createPlaybook

Defines an automatic response: actions that run when a new detection matches the trigger.

POST/api/iam/threats/createPlaybook
client.threats.createPlaybook()Credential

Used inShared Signals receiver,Threat detection

  • Permission: iam:threats:manage on iam/threats/playbooks, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:playbook-create (metadata: name, enabled, trigger, actions).
  • Errors: INVALID_INPUT for a missing or over-long name, a missing trigger, an unknown rule, severity, or subject type, or actions that are not one to five known kinds each listed once (durationMs only on block-network, one minute to 30 days; keepApiKeys only on revoke-sessions); CONFLICT (409) when a playbook has the same name (case-insensitive); LIMIT_EXCEEDED (409) past 50 playbooks; RECENT_AUTH_REQUIRED.

The trigger matches when every clause it sets matches: ruleIds, minSeverity, and subjectTypes; trigger: {} matches every detection. Actions run in order as the threat-detection actor, under the protections in Responses and protections, and playbooks run in the order they were created. Playbooks are the only automatic source of alert emails. A new playbook is enabled unless enabled: false.

await iam.api.threats.createPlaybook(credential, {
  tenantId,
  name: 'Contain taken-over accounts',
  trigger: { ruleIds: ['session-hijack', 'account-takeover-persistence', 'user-reported'] },
  actions: [{ kind: 'contain' }, { kind: 'notify' }],
});
Input

Prop

Type

Returns

A ThreatPlaybook object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/createPlaybook" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "name": "<name>",
  "trigger": {},
  "actions": [
    {
      "kind": "notify"
    }
  ],
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.createPlaybook(
  credential: CredentialInput,
  input: ThreatPlaybookInput & { tenantId: string },
): Promise<ThreatPlaybook>

deletePlaybook

Deletes a playbook; responses it already took stay on their incidents.

POST/api/iam/threats/deletePlaybook
client.threats.deletePlaybook()Credential
  • Permission: iam:threats:manage on iam/threats/playbooks/{id}, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:playbook-delete (metadata: name).
  • Errors: NOT_FOUND when the playbook is not in this tenant; RECENT_AUTH_REQUIRED.

To stop a playbook for a while and keep it, set enabled: false with updatePlaybook instead.

Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/deletePlaybook" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "playbookId": "<playbookId>"
}'
Signature
iam.api.threats.deletePlaybook(
  credential: CredentialInput,
  input: { tenantId: string; playbookId: string },
): Promise<{ deleted: true }>

detect

Runs detection for the tenant now instead of waiting for the scheduled job, and returns what the run did.

POST/api/iam/threats/detect
client.threats.detect()Credential
  • Permission: iam:threats:manage on iam/threats/detections.
  • Audited as: iam:threats:manage, plus whatever the run records (threat:detection, threat:incident-open, playbook responses) by threat-detection.

It is exactly one tenant's share of iam.detectThreats(): it reads the unread events from the tenant's cursor (at most 2000), so it is safe beside the scheduled job and never records anything twice. The result is { tenants, eventsScanned, detections, incidentsOpened, responses, braked, chainBreaks, pending }. Use it behind a "Check now" button or in tests.

Input

Prop

Type

Returns

A ThreatDetectionRun object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/detect" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.detect(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<ThreatDetectionRun>

dismissDetection

Marks an open detection as a false alarm and takes its points out of the identity's risk.

POST/api/iam/threats/dismissDetection
client.threats.dismissDetection()Credential
  • Permission: iam:threats:manage on iam/threats/detections/{id}, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:detection-dismiss (metadata: ruleId, severity, reason, identityId, incidentId), plus threat:risk-change when the identity's level drops.
  • Errors: INVALID_TRANSITION (409) when the detection is not open; ACCESS_DENIED when it is about the caller and the caller is not a root administrator; INVALID_INPUT for a missing reason or one over 512 characters; NOT_FOUND; RECENT_AUTH_REQUIRED.

The detection keeps its place in its incident, marked dismissed with who, when, and why. The incident stays open, even when nothing open is left in it, until someone resolves it with resolveIncident.

Input

Prop

Type

Returns

A ThreatDetection object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/dismissDetection" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "detectionId": "<detectionId>",
  "reason": "<reason>"
}'
Signature
iam.api.threats.dismissDetection(
  credential: CredentialInput,
  input: { tenantId: string; detectionId: string; reason: string },
): Promise<ThreatDetection>

getDetection

Returns one detection with its evidence.

POST/api/iam/threats/getDetection
client.threats.getDetection()Credential
  • Permission: iam:threats:read on iam/threats/detections/{id}.
  • Audited as: iam:threats:read.
  • Errors: NOT_FOUND when the detection is not in this tenant.

A detection names its ruleId, severity, title and one-sentence summary, its subject, the identityId whose risk it raised and the network it came from when there are ones, its incidentId, status (open, dismissed, or resolved), when the activity happened (occurredAt) and when it was recorded (detectedAt), rule-specific metadata, and evidence with up to 20 audit event ids to open in the audit log.

Input

Prop

Type

Returns

A ThreatDetection object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/getDetection" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "detectionId": "<detectionId>"
}'
Signature
iam.api.threats.getDetection(
  credential: CredentialInput,
  input: { tenantId: string; detectionId: string },
): Promise<ThreatDetection>

getIncident

Returns an incident with its detections, notes, responses, and the risk of the identity it concerns.

POST/api/iam/threats/getIncident
client.threats.getIncident()Credential

Used inThreat detection

  • Permission: iam:threats:read on iam/threats/incidents/{id}.
  • Audited as: iam:threats:read.
  • Errors: NOT_FOUND when the incident is not in this tenant.

The result is { incident, detections, notes, responses, risk }: up to 200 detections newest first, notes oldest first, responses (applied and skipped, by people and playbooks) newest first, and, for an incident about an identity, that identity's risk as getRisk returns it. Everything an investigator needs for one page.

Input

Prop

Type

Returns

A IncidentDetail object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/getIncident" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "incidentId": "<incidentId>"
}'
Signature
iam.api.threats.getIncident(
  credential: CredentialInput,
  input: { tenantId: string; incidentId: string },
): Promise<IncidentDetail>

getRisk

Returns one identity's risk: score, level, override, containment, and the contributions behind them.

POST/api/iam/threats/getRisk
client.threats.getRisk()Credential
  • Permission: iam:threats:read on iam/threats/risk/{identityId}.
  • Audited as: iam:threats:read.
  • Errors: NOT_FOUND when the identity is not in this tenant.

score and level are the effective values at the time of the read, decay and override included; level is none for an identity nothing was ever detected about. override shows an administrator's floor with active: false once it has expired, contained is set while the threats module holds the identity contained, and contributions list each detection's points with what they still add (current), newest first.

Input

Prop

Type

Returns

A IdentityRiskView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/getRisk" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>"
}'
Signature
iam.api.threats.getRisk(
  credential: CredentialInput,
  input: { tenantId: string; identityId: string },
): Promise<IdentityRiskView>

getSettings

Returns the tenant's detection settings with every default applied.

POST/api/iam/threats/getSettings
client.threats.getSettings()Credential
  • Permission: iam:threats:read on iam/threats/settings.
  • Audited as: iam:threats:read.

The result has every rule's enabled and severity (and threshold and windowMs where the rule has them), trustedNetworks, dormantDays, riskHalfLifeHours, notify, maxAutomaticContainments, and configured: false while the tenant has never saved settings, with updatedAt and updatedBy once it has.

Input

Prop

Type

Returns

A ThreatSettingsView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/getSettings" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.getSettings(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<ThreatSettingsView>

listDetections

Lists the tenant's detections, newest first, optionally narrowed by status, rule, severity, identity, incident, and time.

POST/api/iam/threats/listDetections
client.threats.listDetections()Credential
  • Permission: iam:threats:read on iam/threats/detections.
  • Audited as: iam:threats:read.
  • Errors: INVALID_INPUT for an unknown status, ruleId, or severity, a limit outside 1 to 1000, or a malformed offset or since.

since (epoch milliseconds) keeps detections recorded from then on. The result is { detections, total }; page with limit (100 by default) and offset.

Input

Prop

Type

Returns

A DetectionPage object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/listDetections" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.listDetections(
  credential: CredentialInput,
  input: {
    tenantId: string;
    status?: DetectionStatus;
    ruleId?: ThreatRuleId;
    severity?: ThreatSeverity;
    identityId?: string;
    incidentId?: string;
    since?: number;
    limit?: number;
    offset?: number;
  },
): Promise<DetectionPage>

listIncidents

Lists the tenant's incidents, most recently active first, optionally narrowed by status, severity, assignee, and identity.

POST/api/iam/threats/listIncidents
client.threats.listIncidents()Credential

Used inThreat detection

  • Permission: iam:threats:read on iam/threats/incidents.
  • Audited as: iam:threats:read.
  • Errors: INVALID_INPUT for an unknown status or severity, or a limit outside 1 to 1000.

Incidents are ordered by their latest detection. status is open, investigating, or resolved. The result is { incidents, total }; page with limit (100 by default) and offset.

Input

Prop

Type

Returns

A IncidentPage object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/listIncidents" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.listIncidents(
  credential: CredentialInput,
  input: {
    tenantId: string;
    status?: IncidentStatus;
    severity?: ThreatSeverity;
    assigneeId?: string;
    identityId?: string;
    limit?: number;
    offset?: number;
  },
): Promise<IncidentPage>

listPlaybooks

Lists the tenant's playbooks by name.

POST/api/iam/threats/listPlaybooks
client.threats.listPlaybooks()Credential
  • Permission: iam:threats:read on iam/threats/playbooks.
  • Audited as: iam:threats:read.

Each playbook shows its trigger, actions, enabled, and how often it ran (runs, lastRunAt).

Input

Prop

Type

Returns

An array of ThreatPlaybook.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/listPlaybooks" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.listPlaybooks(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<ThreatPlaybook[]>

listRisk

Lists the identities at or above a risk level, and contained ones, highest score first.

POST/api/iam/threats/listRisk
client.threats.listRisk()Credential
  • Permission: iam:threats:read on iam/threats/risk.
  • Audited as: iam:threats:read.
  • Errors: INVALID_INPUT for a minLevel other than low, medium, or high, or a limit outside 1 to 1000.

minLevel defaults to low, so the list is everyone with any live risk. Each entry has the same shape as getRisk. The result is { identities, total }; page with limit (100 by default) and offset. Deleted identities are left out.

Input

Prop

Type

Returns

A RiskPage object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/listRisk" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.listRisk(
  credential: CredentialInput,
  input: {
    tenantId: string;
    minLevel?: Exclude<RiskLevel, 'none'>;
    limit?: number;
    offset?: number;
  },
): Promise<RiskPage>

release

Lifts a containment, so the identity is active again and its API keys work.

POST/api/iam/threats/release
client.threats.release()Credential
  • Permission: iam:threats:respond on iam/threats/responses, and a recent sign-in.
  • Audited as: iam:threats:respond and threat:release (metadata: note, containedAt, incidentId).
  • Errors: INVALID_TRANSITION (409) when the identity is not contained by the threats module, no longer disabled, or disabled, suspended, or re-enabled another way since (setStatus, offboarding, agents.suspend), or when it has passed its expiresAt (extend or clear it first); ACCESS_DENIED for a root administrator released by anyone but root; INVALID_INPUT for a note over 512 characters; NOT_FOUND; RECENT_AUTH_REQUIRED.

The identity signs in as usual again; the sessions containment ended stay ended, the member invitations it sent stay revoked, and its risk score is kept. Changing the identity's status any other way (for example identities.setStatus, identities.offboard, or agents.suspend) ends the containment, so a release never undoes it. Its rule-based access packages are re-evaluated right after. The result is the release response, filed under the incident the containment was for.

Input

Prop

Type

Returns

A ThreatResponse object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/release" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>"
}'
Signature
iam.api.threats.release(
  credential: CredentialInput,
  input: { tenantId: string; identityId: string; note?: string },
): Promise<ThreatResponse>

reportSuspicious

Lets a person report activity on their own account that was not them: it raises an incident and signs out everywhere else.

POST/api/iam/threats/reportSuspicious
client.threats.reportSuspicious()Credential

Used inThreat detection

  • Permission: None beyond an ordinary sign-in session of the account's own tenant.
  • Audited as: threat:user-report (metadata: detectionId, incidentId, sessionsEnded, devicesForgotten, withNote, sessionId), with the responses as threat:revoke-sessions and threat:forget-devices.
  • Errors: ACCESS_DENIED for an API key, role session, session token, delegated session, or a session of another tenant; IMPERSONATION_RESTRICTED (403) from a "view as" session; LIMIT_EXCEEDED (409) after five reports in 24 hours; FEATURE_DISABLED (403) when the tenant turned the user-reported rule off; NOT_FOUND for a sessionId that belongs to someone else; INVALID_INPUT for a note over 1000 characters.

It records a user-reported detection (high), which raises the person's risk and opens an incident administrators see, then ends every other session of the account except API keys (the reporting session stays), deletes pending sign-in challenges, forgets remembered devices, and runs the tenant's playbooks. sessionId names a session from auth.listSessions the person does not recognize. The result is { detectionId, incidentId, sessionsEnded, devicesForgotten }. Suggest a password change and a new second factor next.

await client.threats.reportSuspicious({ tenantId, note: 'A sign-in email from a city I have never visited.' });
Input

Prop

Type

Returns

A SuspiciousActivityReport object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/reportSuspicious" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.reportSuspicious(
  credential: CredentialInput,
  input: { tenantId: string; note?: string; sessionId?: string },
): Promise<SuspiciousActivityReport>

resolveIncident

Closes an incident as a true positive, a false positive, or benign.

POST/api/iam/threats/resolveIncident
client.threats.resolveIncident()Credential

Used inThreat detection

  • Permission: iam:threats:manage on iam/threats/incidents/{id}, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:incident-resolve (metadata: resolution, severity, subjectType, subjectId, detectionsResolved, identityId), plus threat:risk-change for identities whose level drops.
  • Errors: INVALID_TRANSITION (409) when it is already resolved; ACCESS_DENIED when a false-positive or benign resolution would clear risk raised against the caller and the caller is not a root administrator; INVALID_INPUT for an unknown resolution or a note over 4000 characters; NOT_FOUND; RECENT_AUTH_REQUIRED.

The incident's open detections become resolved. A false-positive or benign resolution also takes their points out of the identities' risk; true-positive keeps them decaying. An optional note is added as the closing note. Resolved incidents are closed to changes, and the next detection about the same subject opens a new incident.

Input

Prop

Type

Returns

A ThreatIncident object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/resolveIncident" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "incidentId": "<incidentId>",
  "resolution": "true-positive"
}'
Signature
iam.api.threats.resolveIncident(
  credential: CredentialInput,
  input: {
    tenantId: string;
    incidentId: string;
    resolution: IncidentResolution;
    note?: string;
  },
): Promise<ThreatIncident>

respond

Responds by hand to an incident, an identity, or a network with one to five response actions.

POST/api/iam/threats/respond
client.threats.respond()Credential

Used inThreat detection

  • Permission: iam:threats:respond on iam/threats/incidents/{id} (on iam/threats/responses without an incident), and a recent sign-in.
  • Audited as: iam:threats:respond, and each applied action as threat:revoke-sessions, threat:forget-devices, threat:contain, threat:block-network, or threat:notify.
  • Errors: INVALID_INPUT without exactly one of incidentId, identityId, and network, for a network that is not an address or CIDR block, actions that are not one to five known kinds each listed once, a missing reason, containing yourself, or blocking a network that covers your own address; ACCESS_DENIED for ending a root administrator's sessions or containing one without being root, containing an owner without being an owner of the tenant in person or root, or blocking a network on the root tenant without being root; LAST_OWNER (409) for containing the last owner; NOT_FOUND; RECENT_AUTH_REQUIRED.

An incident target acts on the incident's subject (and its identity and network). An incident about the tenant, a network, or a connection names an identity only while all its detections that name one name the same one; once two differ (severalIdentities), identity actions on it are skipped as not-identity, so respond to the person by identityId instead. A response to an identity or network that has an open incident is filed under it. The result lists one response per action, applied or skipped with a reason such as not-identity, no-network, trusted-network, already-applied, inactive, no-incident, no-transport, or no-recipients. Revoking your own sessions keeps the one you are responding from. Containment and network blocks take effect at once; contained identities' rule-based access packages are re-evaluated right after.

await iam.api.threats.respond(credential, {
  tenantId,
  incidentId,
  actions: [{ kind: 'contain' }, { kind: 'block-network', durationMs: 7 * 86_400_000 }, { kind: 'notify' }],
  reason: 'Password sprayed and reused from a hosting provider',
});
Input

Prop

Type

Returns

An array of ThreatResponse.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/respond" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "actions": [
    {
      "kind": "notify"
    }
  ],
  "reason": "<reason>"
}'
Signature
iam.api.threats.respond(
  credential: CredentialInput,
  input: {
    tenantId: string;
    incidentId?: string;
    identityId?: string;
    network?: string;
    actions: ResponseAction[];
    reason: string;
  },
): Promise<ThreatResponse[]>

rules

Lists every detection rule with its defaults, tuning bounds, and the setting in force for the tenant.

POST/api/iam/threats/rules
client.threats.rules()Credential
  • Permission: iam:threats:read on iam/threats/rules.
  • Audited as: iam:threats:read.

Each rule has its id, title, description, category, subject type, MITRE ATT&CK technique, its defaults, the tunable bounds configure accepts for threshold and windowMs, the effective enabled, severity, threshold, and windowMs, everyone for new-network, and customized when the tenant changed anything about it. Build a settings page from it rather than hard-coding the catalog.

Input

Prop

Type

Returns

An array of ThreatRuleView.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/rules" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.rules(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<ThreatRuleView[]>

setRisk

Sets an identity's risk level by hand, as a floor that holds until it expires, or clears it.

POST/api/iam/threats/setRisk
client.threats.setRisk()Credential
  • Permission: iam:threats:respond on iam/threats/risk/{identityId}, and a recent sign-in.
  • Audited as: iam:threats:respond and threat:risk-override (metadata: level, reason, expiresAt, and clearedContributions for none), plus threat:risk-change when the effective level moves.
  • Errors: ACCESS_DENIED for your own identity; INVALID_INPUT for an unknown level, a missing reason, an expiresInMs outside one hour to 90 days, or expiresInMs with none; NOT_FOUND; RECENT_AUTH_REQUIRED.

low, medium, or high records a floor ("confirmed compromised"): the effective level is never below it until expiresInMs passes, or indefinitely without it. none removes the override and every detection's contribution, so the identity starts from zero. The result is the identity's risk as getRisk returns it. Policies see the new level on their next decision.

Input

Prop

Type

Returns

A IdentityRiskView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/setRisk" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>",
  "level": "high",
  "reason": "<reason>"
}'
Signature
iam.api.threats.setRisk(
  credential: CredentialInput,
  input: {
    tenantId: string;
    identityId: string;
    level: RiskLevel;
    reason: string;
    expiresInMs?: number;
  },
): Promise<IdentityRiskView>

summary

Returns the tenant's threat posture at a glance for a dashboard.

POST/api/iam/threats/summary
client.threats.summary()Credential

Used inThreat detection

  • Permission: iam:threats:read on iam/threats/incidents.
  • Audited as: iam:threats:read.

The result is openIncidents (open incidents by severity), investigating (the count under investigation), riskyIdentities (by level), contained, detections24h with byRule24h, and lastRunAt, when detection last read the tenant's audit trail. A lastRunAt far in the past means the job is not running.

Input

Prop

Type

Returns

A ThreatSummary object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/summary" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.threats.summary(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<ThreatSummary>

timeline

Returns an identity's recent audit trail, newest first: what it did and what was done to it.

POST/api/iam/threats/timeline
client.threats.timeline()Credential

Used inThreat detection

  • Permission: iam:threats:read on iam/threats/risk/{identityId}.
  • Audited as: iam:threats:read.
  • Errors: NOT_FOUND when the identity is not in this tenant; INVALID_INPUT for a limit outside 1 to 500.

It lists events the identity performed and events whose resource is the identity, allowed and denied, from since (epoch milliseconds; seven days ago by default), at most limit (200 by default).

Input

Prop

Type

Returns

An array of AuditEvent.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/timeline" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>"
}'
Signature
iam.api.threats.timeline(
  credential: CredentialInput,
  input: { tenantId: string; identityId: string; since?: number; limit?: number },
): Promise<AuditEvent[]>

updateIncident

Moves an incident between open and investigating, assigns it, or changes its severity.

POST/api/iam/threats/updateIncident
client.threats.updateIncident()Credential

Used inThreat detection

  • Permission: iam:threats:manage on iam/threats/incidents/{id}.
  • Audited as: iam:threats:manage and threat:incident-update (metadata: changes, status, severity, assigneeId).
  • Errors: INVALID_TRANSITION (409) for a resolved incident; INVALID_INPUT for a status other than open or investigating, an unknown severity, or an assignee who is not active; NOT_FOUND for the incident or an assignee outside the tenant.

assigneeId: null unassigns. Resolve an incident with resolveIncident. No recent sign-in is needed, so triage stays quick.

Input

Prop

Type

Returns

A ThreatIncident object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/updateIncident" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "incidentId": "<incidentId>"
}'
Signature
iam.api.threats.updateIncident(
  credential: CredentialInput,
  input: {
    tenantId: string;
    incidentId: string;
    status?: Exclude<IncidentStatus, 'resolved'>;
    assigneeId?: string | null;
    severity?: ThreatSeverity;
  },
): Promise<ThreatIncident>

updatePlaybook

Changes a playbook's name, description, trigger, actions, or whether it is enabled.

POST/api/iam/threats/updatePlaybook
client.threats.updatePlaybook()Credential
  • Permission: iam:threats:manage on iam/threats/playbooks/{id}, and a recent sign-in.
  • Audited as: iam:threats:manage and threat:playbook-update (metadata: the settings before and after).
  • Errors: as for createPlaybook, plus NOT_FOUND when the playbook is not in this tenant.

Fields left out keep their value; description: null clears the description, and trigger and actions are replaced whole. The playbook keeps its run count.

Input

Prop

Type

Returns

A ThreatPlaybook object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/threats/updatePlaybook" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "playbookId": "<playbookId>"
}'
Signature
iam.api.threats.updatePlaybook(
  credential: CredentialInput,
  input: Partial<Omit<ThreatPlaybookInput, 'description'>> & {
    tenantId: string;
    playbookId: string;
    description?: string | null;
  },
): Promise<ThreatPlaybook>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page