Summary and recommendation
The Abnormal Security REST API is versioned at /v1/ and authenticates via a static Bearer token issued from the portal under Settings → Integrations → API Keys.
Token permissions are not scope-gated at the token level;
they inherit the portal role of the creating user, meaning tokens created by non-admin users may return HTTP 403 on certain endpoints.
The API is strictly read-only for account and user data - there are no write, update, or delete operations on user records.
Pagination is 1-based integer (pageNumber);
iterate until nextPageNumber is null in the response body.
Rate limits are enforced but thresholds and retry headers are not publicly documented;
implement exponential backoff on HTTP 429.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (static API key issued from the Abnormal Security portal) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | N/A |
Authentication
Auth method: Bearer token (static API key issued from the Abnormal Security portal)
Setup steps
- Log in to the Abnormal Security portal (portal.abnormalsecurity.com).
- Navigate to Settings → Integrations → API Keys.
- Click 'Create API Key', assign a name and optional expiry, then copy the generated token.
- Include the token in every request as the HTTP header: 'Authorization: Bearer
'.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| N/A | Abnormal Security API tokens are not scope-gated at the token level; access is determined by the portal role of the user who created the token. | All endpoints |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| string | Primary email address of the employee/account. | N/A – read-only from Abnormal's detection data | N/A | Used as the unique identifier for account lookups. | |
| name | string | Display name of the employee. | N/A | N/A | Populated from the connected mail environment. |
| riskScore | string | Abnormal-computed risk score for the account (e.g., 'high', 'medium', 'low'). | N/A | N/A | Reflects behavioral and threat signals. |
| riskLevel | string | Categorical risk level derived from riskScore. | N/A | N/A | |
| compromisedSince | string (ISO 8601) | Timestamp when the account was first flagged as compromised. | N/A | N/A | Null if account is not currently compromised. |
| isVip | boolean | Whether the account is marked as a VIP/executive. | N/A | N/A | |
| department | string | Department of the employee as sourced from the mail environment. | N/A | N/A | |
| title | string | Job title of the employee. | N/A | N/A | |
| isActive | boolean | Whether the account is currently active in the mail environment. | N/A | N/A |
Core endpoints
List accounts
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/accounts - Watch out for: Returns accounts Abnormal has observed; it is not a writable directory. pageNumber is 1-based.
Request example
GET /v1/accounts?pageNumber=1 HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"accounts": [
{"email": "user@example.com", "name": "Jane Doe", "riskScore": "high"}
],
"nextPageNumber": 2
}
Get account details
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/accounts/{email} - Watch out for: The email address must be URL-encoded in the path.
Request example
GET /v1/accounts/user%40example.com HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"email": "user@example.com",
"name": "Jane Doe",
"riskScore": "high",
"isVip": false,
"department": "Finance"
}
Get account-related cases
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/accounts/{email}/cases - Watch out for: Returns ATO (account takeover) cases linked to the account, not general threat messages.
Request example
GET /v1/accounts/user%40example.com/cases HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"cases": [
{"caseId": "1234", "severity": "HIGH", "status": "Open"}
]
}
List threats (messages)
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/threats - Watch out for: Threats are message-level objects, not user objects. Filter by recipient to correlate with a specific user.
Request example
GET /v1/threats?pageNumber=1 HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"threats": [
{"threatId": "abc123", "subject": "Invoice", "receivedTime": "2024-01-10T12:00:00Z"}
],
"nextPageNumber": 2
}
Get threat details
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/threats/{threatId}
Request example
GET /v1/threats/abc123 HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"threatId": "abc123",
"subject": "Invoice",
"attackType": "BEC",
"recipientAddress": "user@example.com"
}
List cases
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/cases - Watch out for: Cases represent ATO investigations; severity values are HIGH, MEDIUM, LOW.
Request example
GET /v1/cases?pageNumber=1 HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"cases": [
{"caseId": "1234", "severity": "HIGH", "affectedEmployee": "user@example.com"}
],
"nextPageNumber": null
}
Get case details
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/cases/{caseId}
Request example
GET /v1/cases/1234 HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"caseId": "1234",
"severity": "HIGH",
"status": "Open",
"affectedEmployee": "user@example.com",
"firstObserved": "2024-01-09T08:00:00Z"
}
Get vendor (third-party) details
- Method: GET
- URL:
https://api.abnormalsecurity.com/v1/vendors/{vendorDomain} - Watch out for: Vendor endpoints are part of Vendor Email Compromise (VEC) detection; availability may depend on licensed modules.
Request example
GET /v1/vendors/supplier.com HTTP/1.1
Host: api.abnormalsecurity.com
Authorization: Bearer <token>
Response example
{
"vendorDomain": "supplier.com",
"riskScore": "medium",
"engagementHistory": []
}
Rate limits, pagination, and events
Rate limits: Abnormal Security enforces rate limits but does not publicly document specific numeric thresholds in its official docs as of the policy date.
Rate-limit headers: Unknown
Retry-After header: Unknown
Rate-limit notes: HTTP 429 is returned when limits are exceeded. Specific per-plan limits and header names are not documented publicly.
Pagination method: cursor
Default page size: 100
Max page size: 100
Pagination pointer: pageNumber (1-based integer); some endpoints use a cursor token returned in the response body as 'nextPageNumber'.
Webhooks available: No
Webhook notes: Abnormal Security does not document a native outbound webhook system in its public API reference as of the policy date.
Alternative event strategy: Use polling on /v1/threats or /v1/cases with a timestamp or pageNumber cursor to detect new events. SIEM integrations (Splunk, Microsoft Sentinel) are available via pre-built connectors in the Abnormal portal.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: N/A
- Endpoint: Not documented
Limitations:
- Abnormal Security does not expose a SCIM 2.0 endpoint for user provisioning or deprovisioning.
- User identity data is ingested read-only from the connected mail environment (Microsoft 365 or Google Workspace); it is not managed via SCIM.
- SSO via Okta or Entra ID is supported for portal login but does not include SCIM provisioning.
Common scenarios
Three primary automation scenarios are supported by the current API surface.
First, enumerating high-risk accounts: paginate GET /v1/accounts, filter on riskScore == 'high', then hydrate each match with GET /v1/accounts/{email} (URL-encode the @ as %40) to retrieve department, title, and isVip for downstream identity graph enrichment or security review routing.
Second, pulling open ATO cases per user: call GET /v1/accounts/{email}/cases, filter for status == 'Open', then fetch full investigation detail via GET /v1/cases/{caseId}
this endpoint returns only ATO-type cases, not phishing message threats.
Third, polling for new threats: since no server-side recipient or date-range filter exists on GET /v1/threats, client-side filtering against recipientAddress and receivedTime is required, which can be expensive for large tenants.
The /v1/vendors/{vendorDomain} endpoint is gated behind the Vendor Email Compromise licensed module and will return HTTP 403 without it.
Identify high-risk employees for security review
- GET /v1/accounts?pageNumber=1 to retrieve all observed accounts.
- Paginate through all pages using the 'nextPageNumber' field until it is null.
- Filter the resulting list for records where riskScore == 'high'.
- For each high-risk email, call GET /v1/accounts/{email} to retrieve full profile including department and title.
- Cross-reference with your HR system or IdP to initiate a security review workflow.
Watch out for: riskScore values and their exact string representations should be validated against the live API response; the API does not expose an enum list in the public docs.
Pull open ATO cases for a specific user
- Call GET /v1/accounts/{email}/cases using the URL-encoded employee email.
- Filter returned cases where status == 'Open'.
- For each open caseId, call GET /v1/cases/{caseId} to retrieve full investigation details including firstObserved and severity.
- Feed case data into your SIEM or ticketing system.
Watch out for: Cases endpoint returns only ATO-type investigations linked to the account, not phishing message threats. Use /v1/threats filtered by recipient for message-level data.
Poll for new threats targeting a specific recipient
- Call GET /v1/threats?pageNumber=1 and record the most recent 'receivedTime' from the response.
- On subsequent polls, retrieve pages and stop pagination when 'receivedTime' is older than the last recorded timestamp.
- Filter threats where 'recipientAddress' matches the target user email.
- Store new threat IDs and call GET /v1/threats/{threatId} for full details as needed.
Watch out for: There is no server-side filter parameter for recipient or date range on the /v1/threats list endpoint; client-side filtering is required, which may be expensive for large tenants.
Why building this yourself is a trap
The most consequential caveat for integration architects is that Abnormal's account objects are an observed, read-only reflection of the mail environment - not a canonical identity directory. The API cannot be used to provision, deprovision, or modify user records, and account presence in the API does not confirm active employment or current access rights.
For identity graph construction, treat Abnormal data as a risk-signal enrichment layer: riskScore, riskLevel, compromisedSince, and isVip are high-value fields to join against your IdP's user records, but the join key (email) must be URL-encoded in every path parameter. Webhooks are not natively supported;
all event detection requires polling, and no public deprecation timeline exists for the v1 API path.
Automate Abnormal Security workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.