API Reference
The read-only REST API for pulling 1Security detections, Microsoft security alerts, audit logs, policy scans, and the notification log into your own tools - SIEM, SOC, or MSSP.
The 1Security REST API lets a SOC, MSSP, or SIEM pull your tenant's activity and detections on a schedule. It is read-only: a key can read the tenant's data and cannot change anything in 1Security or in your Microsoft 365 tenant.
| Resource | Endpoint | What it is |
|---|---|---|
| Audit logs | /logs | Normalized M365 activity, enriched by 1Security |
| Detections | /detections | 1Security's own detection episodes (anomalies today, more to come) |
| Security alerts | /security-alerts | Microsoft Defender / Sentinel-sourced alerts |
| Notifications | /notifications | The notification log - every decision: sent, suppressed, failed |
| Policy scans | /policy-scans | Policy evaluation snapshots - "at time T, policy P matched N" |
| Evidence pack | /evidence/agents | Dated AI agent governance evidence pack for auditors |
| Actions | /actions | The remediation ledger - staged, approved, executed, failed, reverted |
| Compliance status | /compliance/status | Per-framework compliance readiness - every control with its status and metrics |
| Compliance snapshots | /compliance/snapshots | The dated snapshot history behind the status - itself evidence of ongoing oversight |
This page is the contract: endpoints, parameters, fields, and error codes. A
machine-readable version is served at GET /api/v1/openapi.json (OpenAPI 3.1,
no authentication required) - import it into Postman or a client generator.
For how to wire it into a specific SIEM, see the
SIEM integration guide.
Delivery is pull-based: you poll, we do not push. Outbound webhook delivery is planned - until it ships, the polling pattern in the integration guide is the supported approach.
Base URL
The API is served from the same deployment that runs your 1Security instance,
under the /api/v1 prefix.
| Deployment | Base URL |
|---|---|
| Cloud (SaaS) | https://api.1security.ai/api/v1 |
| BYOC / On-Premise | https://<your-1security-host>/api/v1 |
In a BYOC or on-premise deployment the API stays inside your own network perimeter. Everything else on this page is identical across deployments.
All examples below use the SaaS base URL and assume the key is in an environment variable:
export ONESEC_API_KEY="1sec_live_…"Authentication
Every request is authenticated with an API key. A regular key is bound to exactly one tenant and grants read access to that tenant's data only; an organization-wide key (MSSP) reads any tenant of its organization, selected per request.
Creating a key
In the dashboard, go to Settings → API and choose Create API key. The API keys tab lists every key with its scopes, status, and last use. Key management is admin-only.
You choose a name, the scopes the key should carry, and optionally an expiry
date. In a multi-tenant dashboard ("All tenants") you also pick which member
tenant the key is for - a key is always bound to one tenant, so a grouped
dashboard needs one key per member (or one
organization-wide key); the keys table shows
every member's keys with a tenant column. The full secret - 1sec_live_… - is displayed once, at creation.
1Security stores only a cryptographic hash of it, so it cannot be shown again
or recovered by anyone, including support. Copy it straight into your SIEM's
credential store.
Sending the key
Pass it as a bearer token (preferred) or in X-API-Key:
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
https://api.1security.ai/api/v1/pingcurl -H "X-API-Key: $ONESEC_API_KEY" \
https://api.1security.ai/api/v1/pingStart with /ping. It confirms the key is valid and shows which tenant and
scopes it maps to, which rules out the most common setup mistakes before you
build a connector around it.
The Settings → API screen also has a Playground tab: pick an
endpoint, fill in parameters, and run the request against your tenant's live
data straight from the browser - with the matching curl command ready to
copy. It is the fastest way to see real payloads and test filters before
writing connector code.
Scopes
Each key carries one or more read scopes. A request to an endpoint whose scope
the key lacks returns 403 - grant only what the integration needs.
Prop
Type
Keys created before /policy-scans was renamed from /monitoring-alerts may
still carry the monitoring-alerts:read scope. It keeps working - the server
treats it as policy-scans:read - but request the new name on new keys.
Organization-wide keys (MSSP)
An MSSP or partner operating several tenants under one organization can create a single organization-wide key instead of one key per tenant: in the create-key dialog, tick Organization-wide key (MSSP) (the option appears only for accounts working inside an organization).
An organization-wide key selects its target tenant per request with the
X-Tenant-Id header - either the 1Security tenant id or the Azure tenant id:
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
-H "X-Tenant-Id: <tenant id or Azure tenant id>" \
"https://api.1security.ai/api/v1/detections"GET /tenants lists every tenant the key can reach, so an integration
iterates: pull the tenant list, then poll each tenant with its id in the
header. The rules:
- A data request without
X-Tenant-Idreturns400 TENANT_REQUIRED; only/pingand/tenantswork without a selected tenant. - A tenant outside the key's organization returns
403 TENANT_NOT_IN_ORGANIZATION- organization membership is the authorization boundary. - The plan gate applies per selected tenant, and the rate limit stays per key - all tenants share the one budget.
- The same header works on the MCP endpoint, so one
MCP server entry per tenant, each with its own
X-Tenant-Id.
Rotating and revoking
Revocation takes effect immediately - the next request with that key returns
401. To rotate without downtime: create the replacement key, deploy it to the
connector, confirm traffic on the new key in Settings → API (each key
shows when it was last used), then revoke the old one.
Treat an API key like a password. Anyone holding it can read the tenant's activity and alerts. Store it in a secret manager, never in a repository or a connector's plain-text configuration file.
Response format
Every successful list response uses the same envelope:
{
"data": [
/* … */
],
"pagination": {
"nextCursor": "eyJvIjo1MH0",
"hasMore": true,
"limit": 50
}
}Single-object endpoints (/ping, /security-alerts/{id}) return { "data": { … } }
with no pagination block.
Fields are whitelisted per endpoint, so the shape is stable: new fields may be added over time, existing ones are not removed or renamed without notice.
Timestamps
All timestamps are UTC and use full ISO-8601 (2026-06-05T09:12:44Z) on every
endpoint. Earlier releases returned policy-scan timestamps without the T
separator and Z designator; that is fixed.
Pagination
List endpoints return at most limit items (default 50, max 1000) plus an
opaque cursor. Pass the returned nextCursor back as ?cursor= to fetch the
next page. When hasMore is false, nextCursor is null and you have
reached the end.
Treat the cursor as meaningless - echo it back unchanged. Its encoding is an implementation detail and will change.
Page over a closed time window. Paging a live, open-ended result set is
not safe: new events keep arriving at the head of the ordering while you page,
which shifts rows between requests. Bound both ends of the window
(discoveredFrom and discoveredTo on /logs, from and to on
the other feeds) and the set stops changing underneath you while you drain it.
The integration guide turns this into a
concrete polling loop.
For a fully deterministic drain on /logs, add sort=discoveredAtAsc: that
ordering is tie-broken by event id, so every row has exactly one position. On
the other feeds, rows sharing an identical sort value have no guaranteed
relative order, so keep windows narrow and deduplicate on id.
Rate limits
Keys are limited to 600 requests per minute, applied per key on a best-effort basis. Successful responses carry:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | When the window resets, in epoch seconds |
Exceeding the limit returns 429 with a Retry-After header. Polling each
endpoint once per minute with a large limit sits far inside the budget; the
limit exists to blunt runaway loops, not to shape normal integration traffic.
Endpoints
GET /ping
Connection test. Returns the tenant and scopes the key maps to. Requires no
particular scope - any valid key works. An organization-wide key without a
tenant selector returns tenantId: null.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
https://api.1security.ai/api/v1/ping{
"data": {
"tenantId": "01H…",
"keyId": "01J…",
"name": "Splunk prod",
"scopes": ["logs:read", "detections:read", "security-alerts:read"],
"organizationWide": false
}
}GET /tenants
The tenants this key can read. A tenant-bound key returns its single tenant;
an organization-wide key returns every tenant of its organization - the ids an
integration iterates with X-Tenant-Id. Requires no particular scope.
{
"data": [
{ "id": "01H…", "name": "Contoso", "azureTenantId": "d3adb33f-…" }
]
}GET /logs
Normalized M365 activity events, enriched with the actor, resource, application,
device, and location 1Security resolved for each one. Requires logs:read.
Two different times are recorded per event, and the difference matters when polling:
occurredAt- when the action happened in Microsoft 365.discoveredAt- when 1Security ingested it. M365 can surface events well after the fact, so this is the one to poll on. An event that arrives late has an oldoccurredAtbut a currentdiscoveredAt.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/logs?severity=high,critical&limit=100"{
"data": [
{
"id": "01J…",
"occurredAt": "2026-06-05T09:12:44Z",
"discoveredAt": "2026-06-05T09:13:01Z",
"action": "FileDownloaded",
"description": "Downloaded Q3-forecast.xlsx",
"severity": "high",
"actorId": "01H…",
"actorName": "jane@contoso.com",
"actorType": "user",
"actorIp": "20.42.0.0",
"resourceId": "01H…",
"resourceName": "Q3-forecast.xlsx",
"resourceType": "file",
"workload": "SharePoint",
"sourceType": "azure",
"sourceName": "Microsoft 365",
"clientApp": "OneDrive Sync",
"deviceId": "01H…",
"deviceName": "LAPTOP-4471",
"isManagedDevice": false,
"applicationId": "01H…",
"applicationClientId": "ab12…",
"applicationDisplayName": "Microsoft SharePoint",
"externalEventId": "…"
}
],
"pagination": { "nextCursor": "eyJvIjoxMDB9", "hasMore": true, "limit": 100 }
}Response fields: id, occurredAt, discoveredAt, action,
description, severity, actorId, actorName, actorType, actorIp,
resourceId, resourceName, resourceType, workload, sourceType,
sourceName, clientApp, deviceId, deviceName, applicationId,
applicationClientId, applicationDisplayName, isManagedDevice,
externalEventId.
GET /detections
1Security's own detection episodes. Requires detections:read.
Rows use a kind-discriminated envelope: the fields below exist for every
detection, and everything specific to one kind rides under details. Today
every row is kind: "anomaly"; new kinds (for example impossible travel or
phishing verdicts) will arrive as new kind values with their own details
shapes - the envelope itself does not change.
By default only alerted-tier episodes are returned - the ones that cleared the
tenant's alert line. Pass includeInfoTier=true to also receive
informational-tier episodes.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/detections?status=open&limit=50"{
"data": [
{
"id": "01J…",
"kind": "anomaly",
"severity": "high",
"status": "open",
"stateful": true,
"openedAt": "2026-06-05T09:12:44Z",
"lastSeenAt": "2026-06-05T11:40:02Z",
"resolvedAt": null,
"summary": "jane@contoso.com performed 412 actions (Permanent deletions) in one day, about 2,475% above their usual 16 per day.",
"detector": { "id": "01J…", "name": "Mass file deletion" },
"entity": { "type": "user", "id": "01H…", "name": "jane@contoso.com" },
"details": {
"level": "alerted",
"current": 412,
"peakCurrent": 412,
"baseline": 16,
"score": 5.8,
"peakScore": 5.8,
"diffPercent": 2475,
"actionGroup": "destructive_permanent",
"activityLabel": "Permanent deletions",
"activityDescription": "Deletions nothing can bring back: hard-deleted mail, files purged past the recycle bin, wiped version history, deleted sites and lists.",
"episodeCount": 1
}
}
],
"pagination": { "nextCursor": null, "hasMore": false, "limit": 50 }
}Response fields: id, kind, severity, status, stateful,
openedAt, lastSeenAt, resolvedAt, summary, detector (id, name),
entity (type, id, name - null for tenant-wide aggregate episodes),
and details (level, current, peakCurrent, baseline, score,
peakScore, diffPercent, actionGroup, activityLabel,
activityDescription, episodeCount).
summary is a ready-made English sentence describing the episode in the
entity's own numbers - suitable as an incident title in a SIEM.
activityLabel and activityDescription are the human-readable mirror of
actionGroup, so consumers never have to decode taxonomy values like
privilege_granted themselves.
Detections are stateful - status and resolvedAt change after the
episode first appears. Pair the incremental poll with an hourly reconciliation
sweep that upserts on id - see the integration
guide.
GET /detections/{id}
One detection episode in full. Unlike the list, no tier gating - a by-id
lookup answers for info-tier episodes too. Requires detections:read.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/detections/01J…"Returns { "data": <detection> } in the same shape as the list items, or
404 NOT_FOUND when the id does not exist in this tenant.
GET /policy-scans
Policy evaluation snapshots: each row records that at scannedAt the policy
matched resources resources. Requires policy-scans:read (keys with the
legacy monitoring-alerts:read scope keep working).
This is a metric series, not an alert feed - forward it when you want posture
counts in the SIEM. A policy whose count crossing a line should notify someone
does that through its alert rule, and the decision lands in /notifications.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/policy-scans?severity=high&limit=50"Response fields: id, name, severity, status, isResolved,
resourceType, resources, assignedUser, description, createdFrom,
scannedAt, resolvedAt.
scannedAt is when the scan ran, and it is the same value that from and
to filter on. Use it as the event time in your SIEM.
GET /policy-scans/{id}
One policy scan snapshot in full. On top of the list shape it carries
policyId and automations (the JSON-stringified automations payload the
scan carried). Requires policy-scans:read.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/policy-scans/01J…"Returns { "data": <scan> }, or 404 NOT_FOUND when the id does not exist
in this tenant.
GET /notifications
The notification log: one row per notification decision - sent, suppressed
with the reason, or failed - across policy digests, instant triggers, and
anomaly notifications. Requires notifications:read.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/notifications?decision=suppressed&from=$(date -u -d '-1 day' +%FT%TZ)"Response fields: id, createdAt, source, kind, channel,
decision, reason, policyId, policyName, subjectType, subjectId,
subjectCount, resourceType, resourceId, severity, recipients,
recipientSource, senderMode, mailSubject.
GET /evidence/agents
The AI agent compliance evidence pack: one dated JSON document covering agent
inventory, permission grants, activity, lifecycle and threat events,
instruction changes, the available-to-install shelf, third-party app access,
and a log retention attestation - each section mapped to the articles it
evidences across every registered framework (EU AI Act, NIS2, Polish KSC and
KRiBSI, GDPR, DORA, ISO/IEC 42001, ISO/IEC 27001, SOC 2, NIST CSF 2.0, HIPAA).
Requires evidence:read.
Optional from and to query parameters (ISO-8601) bound the covered
activity period; the default window is the trailing 183 days. An optional
framework parameter (a registry key such as dora or iso_27001) keeps
only the sections and legend entries for that framework - the same scoped pack
the framework view's Export evidence pack button downloads. Returns
{ "data": <pack> } with no pagination - the pack is one document, identical
to the dashboard's download.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/evidence/agents"The full pack structure, section list, and article mapping live on the Compliance evidence export page.
GET /compliance/status
Compliance readiness in three layers. requirements are what the
organisation does or measures, each evaluated once: status is met,
at_risk, not_met or - honestly - not_evaluated, evaluation says where
the verdict comes from (measured, no_data, attested, manual,
not_applicable), metricsJson carries the numbers, attestation the
compliance officer's record for manual duties, and articles every article
across every framework that cites it. frameworks lists every registered
framework (EU AI Act, NIS2, Polish KSC and KRiBSI, GDPR, DORA, ISO/IEC 42001,
ISO/IEC 27001, SOC 2, NIST CSF 2.0, HIPAA) with selected (declared
applicable by the tenant), readinessPercent and its controls (articles),
each rolled up from the requirements it cites. changes is the per-framework
diff against the latest snapshot. Headline counts (metCount and friends)
are over requirements cited by the selected frameworks, not over articles.
It is the same document the compliance dashboard renders and the weekly
snapshot persists. Requires evidence:read.
Takes no parameters. Returns { "data": <status> } with no pagination - one
document per call, evaluated live.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/compliance/status"GET /compliance/snapshots
The dated snapshot history behind /compliance/status: the weekly cron and
every manual snapshot, newest first, each carrying the full status document as
it was persisted at the time. A stored series of these is itself evidence of
ongoing oversight. Requires evidence:read.
Takes limit and cursor only; items have id, takenAt, trigger
(scheduled or manual), and status (the full document).
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/compliance/snapshots?limit=12"GET /actions
The remediation ledger: every action 1Security staged, an admin approved or
rejected, the platform executed, and an independent probe confirmed - with its
trigger (policy or human), scope, and current attention state. This is the
audit trail behind "what did the platform change in Microsoft 365 and who
authorised it". Requires actions:read.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/actions?rung=applied&from=$(date -u -d '-30 day' +%FT%TZ)"Response fields: id, actionName, actionType, policyId,
policyName, actorId, actorName, actorKind, rollupRung, itemsTotal,
itemsSettled, failedCount, hasFailures, isSimulated, isIrreversible,
revertBucket, resourceType, resourceId, resourceName, askerCount,
askerNames, actionArguments, createdAt, updatedAt, dueAt,
acknowledgedAt.
GET /security-alerts
Microsoft Defender / Sentinel-sourced alerts, linked to the users, groups,
emails, and apps 1Security matched them to. Requires security-alerts:read.
Query parameters
Prop
Type
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
"https://api.1security.ai/api/v1/security-alerts?severity=high&status=new"Response fields: id, title, description, severity, status,
classification, category, threatDisplayName, firstActivityDateTime,
isResolved, users, groups, emails, apps.
GET /security-alerts/{id}
Full detail for a single security alert. Requires security-alerts:read.
Returns 404 if the id does not exist within your tenant.
Use this to enrich an alert already in your SIEM: forward the summary from the list endpoint, then fetch the detail on demand during triage rather than indexing the full payload for every alert.
curl -H "Authorization: Bearer $ONESEC_API_KEY" \
https://api.1security.ai/api/v1/security-alerts/01J…Response fields: everything from the list endpoint plus determination,
assignedTo, alertWebUrl, incidentWebUrl, serviceSource,
detectionSource, createdDateTime, lastUpdateDateTime, resolvedDateTime,
lastActivityDateTime, recommendedActions, actorDisplayName,
threatFamilyName, and rawData - the original provider payload, unmodified.
Errors
Errors use a consistent JSON shape and standard HTTP status codes:
{
"error": {
"code": "UNAUTHENTICATED",
"message": "Invalid, expired, or revoked API key."
}
}Limits of the current API
Being explicit about what is not here yet, so you can design around it:
- No push delivery. Webhook subscriptions are planned; today you poll.
- No write access. Alerts cannot be acknowledged or resolved through the API. Bidirectional sync is planned.
- One tenant per request. An organization-wide key
covers all of an organization's tenants, but each request still addresses
one tenant via
X-Tenant-Id- there is no cross-tenant aggregate endpoint. - No entity lookups. Individual users, files, groups, and apps cannot be queried directly yet; detections currently carry the anomaly kind only.
Next
SIEM Integration
Forward 1Security detections, security alerts, audit logs, and policy scans into Splunk, Microsoft Sentinel, QRadar, Elastic, or any SIEM that can poll a REST endpoint.
MCP Server
Connect AI assistants and agent frameworks to 1Security over the Model Context Protocol - the same read-only data as the REST API, exposed as typed tools with the same keys, scopes, and limits.