API Layer
Implementation status:
- Entity endpoints (incl. batch and versions), path endpoints, finding endpoints, temporal endpoints, sync list + detail, subgraph: [Implemented]
- Execution chain endpoints (
GET /execution-chains,GET /execution-chains/:id): [Implemented]- W1 endpoints (posture summary, risk clusters, exposures): [Implemented] — see W1 Posture & Exposure Endpoints section below
- Standalone events endpoint (
GET /events), sync trigger (POST /syncs), structured query (POST /query), type-specific subtype filters (workload_subtype, connection_subtype, credential_subtype),has_findingsfilter: [Planned]- See ADR-006 for the 9-type entity model. See ADR-010 for the workload rename (
"automation"→"workload").
Overview
The SecurityV0 API layer is the HTTP surface over the StorageAdapter interface. It exposes tenant-scoped REST endpoints for querying the execution/authority graph, managing findings, retrieving evidence packs, and triggering syncs.
Relationship to other API surfaces:
- This document defines the core platform API consumed by the Web UI and API clients
- 06-scim-oaa-integration.md defines interoperability export endpoints (SCIM 2.0 and OAA format) — these are separate concerns with different audiences and response schemas
Auth and gateway: All endpoints are tenant-scoped and require authentication. See 00-overview.md §8 (API Gateway) for auth flows (OAuth 2.0 for web UI, API keys for programmatic access), rate limiting, and tenant routing. This document does not re-specify gateway concerns.
Base URL: https://{host}/api/v1
Entity Endpoints
Entities are the nodes in the execution/authority graph. All 9 entity types (identity, workload, connection, credential, owner, role, permission, resource, execution_evidence) are stored in a single collection discriminated by entity_type. The deprecated alias "automation" is accepted as equivalent to "workload" for backward compatibility (ADR-010). See 01-data-model.md for entity type definitions and ADR-006 for the reclassification rationale.
List Entities [Implemented]
GET /api/v1/entities
Query Parameters:
| Parameter | Type | Description | Status |
|---|---|---|---|
entity_type | string | Filter by type: identity, workload, connection, credential, owner, role, permission, resource, execution_evidence (deprecated alias: automation accepted as workload) | [Implemented] |
source_system | string | Filter by source: entra_id, servicenow, github, aws | [Implemented] |
status | string | Filter by status: active, disabled, deleted, departed, disbanded | [Implemented] |
identity_subtype | string | Filter identities by subtype: service_principal, oauth_app, machine_account, integration_user | [Implemented] |
workload_subtype | string | Filter workloads by subtype: business_rule, script_include, flow_designer_flow, scheduled_job, event_script, transform_map (deprecated alias: automation_subtype) | [Planned] |
connection_subtype | string | Filter connections by subtype: rest_message, rest_method, soap_message, http_connection | [Planned] |
credential_subtype | string | Filter credentials by subtype: oauth_provider, oauth_profile, api_key, certificate, client_secret | [Planned] |
egress_category | string | Filter by egress: identity_provider, itsm, monitoring, unknown | [Implemented] |
ownership_status | string | Filter: owned, orphaned, degraded | [Implemented] |
risk_group | string | Filter by risk grouping | [Implemented] |
execution_mode | string | Filter: autonomous, operator_assisted, human_triggered, unknown | [Implemented] |
security_relevance | string | Filter: active_external, dormant_authority, internal_inventory | [Implemented] |
q | string | Text search across entity names | [Implemented] |
has_findings | boolean | Only return entities with active findings | [Planned] |
cursor | string | Pagination cursor from previous response | [Implemented] |
limit | integer | Results per page (default: 50, max: 200) | [Implemented] |
sort | string | Sort field (e.g., last_activity_at, created_at). Prefix with - for descending. | [Implemented] |
Response:
{
"data": [
{
"_id": "uuid-sp-hr-onboarding",
"tenant_id": "uuid-tenant-...",
"entity_type": "identity",
"source_system": "entra_id",
"source_id": "sp-abc123",
"properties": {
"identity_subtype": "service_principal",
"display_name": "SN-Integration-Prod",
"execution_mode": "autonomous",
"status": "active",
"last_activity_at": "2026-01-22T14:00:00Z",
"source_metadata": {
"appId": "a1b2c3d4-...",
"servicePrincipalType": "Application"
},
"source_metadata_hash": "sha256:e5f6a7b8..."
},
"relationships": [
{ "type": "OWNED_BY", "target_id": "uuid-owner-sarah", "properties": { "since": "2024-06-15" } },
{ "type": "AUTHENTICATES_TO", "target_id": "uuid-identity-sn-integration-user", "properties": {} },
{ "type": "HAS_ROLE", "target_id": "uuid-role-itil", "properties": {} }
],
"sync_version": 42,
"last_synced_at": "2026-01-22T15:00:00Z",
"created_at": "2024-06-15T10:00:00Z",
"updated_at": "2026-01-22T15:00:00Z"
},
{
"_id": "uuid-br-autoclose-incidents",
"tenant_id": "uuid-tenant-...",
"entity_type": "workload",
"source_system": "servicenow",
"source_id": "br-abc456",
"properties": {
"workload_subtype": "business_rule",
"display_name": "Auto-close Resolved Incidents",
"execution_mode": "autonomous",
"security_relevance": "active_external",
"status": "active",
"last_activity_at": "2026-01-22T13:00:00Z"
},
"relationships": [
{ "type": "RUNS_AS", "target_id": "uuid-identity-sn-integration", "properties": {} },
{ "type": "EXECUTES_ON", "target_id": "uuid-res-incident", "properties": {} },
{ "type": "OWNED_BY", "target_id": "uuid-owner-it-ops", "properties": {} }
],
"sync_version": 42,
"last_synced_at": "2026-01-22T15:00:00Z",
"created_at": "2025-11-01T00:00:00Z",
"updated_at": "2026-01-22T15:00:00Z"
}
],
"cursor": {
"next": "eyJsYXN0X2lkIjoiLi4uIn0=",
"has_more": true
},
"meta": {
"total_count": 342
}
}
Notes:
- List responses include full
relationshipsarrays (same shape as detail). The StorageAdapter returns completeEntityDocobjects. entity_typeforworkload,connection, andexecution_evidenceis part of the target 9-type model (ADR-006). The"automation"entity type is deprecated and accepted as an alias for"workload"(ADR-010).
Get Entity [Implemented]
GET /api/v1/entities/{id}
Returns the full entity document including relationships and execution paths (for identities).
Response:
{
"data": {
"_id": "uuid-sp-hr-onboarding",
"tenant_id": "uuid-tenant-...",
"entity_type": "identity",
"source_system": "entra_id",
"source_id": "sp-abc123",
"properties": {
"identity_subtype": "service_principal",
"display_name": "SN-Integration-Prod",
"execution_mode": "autonomous",
"status": "active",
"last_activity_at": "2026-01-22T14:00:00Z",
"source_metadata": { "...": "..." },
"source_metadata_hash": "sha256:e5f6a7b8..."
},
"relationships": [
{
"type": "OWNED_BY",
"target_id": "uuid-owner-sarah",
"target_name": "Sarah Chen",
"target_type": "owner",
"properties": {
"since": "2024-06-15",
"status": "decayed",
"ownership_level": "primary"
}
},
{
"type": "AUTHENTICATES_TO",
"target_id": "uuid-identity-sn-integration-user",
"target_name": "sn-integration-user",
"target_type": "identity",
"properties": {
"auth_protocol": "oauth2",
"target_system": "servicenow",
"trust_chain_position": 0,
"evidence_references": {
"issuing_system_id": "a1b2c3d4-...",
"issuing_tenant_id": "72f988bf-...",
"target_system_id": "a1b2c3d4-...",
"target_instance_id": "https://corp.service-now.com",
"matching_field": "client_id",
"matching_value": "a1b2c3d4-..."
}
}
},
{
"type": "CALLS",
"target_id": "uuid-si-rest-utils",
"target_name": "RestApiUtils",
"target_type": "automation",
"properties": {
"call_type": "include"
}
},
{
"type": "INVOKES",
"target_id": "uuid-conn-graph-api",
"target_name": "Microsoft Graph REST Message",
"target_type": "connection",
"properties": {
"invocation_type": "rest"
}
},
{
"type": "USES",
"target_id": "uuid-cred-oauth-profile",
"target_name": "Graph API OAuth Profile",
"target_type": "credential",
"properties": {
"auth_method": "oauth2"
}
},
{
"type": "AUTHENTICATES_AS",
"target_id": "uuid-sp-graph-integration",
"target_name": "sp-graph-integration",
"target_type": "identity",
"properties": {
"credential_type": "oauth_profile",
"binding_method": "client_credentials"
}
}
],
"execution_paths": [
{
"resource_id": "uuid-res-hr-case",
"resource_name": "hr_case",
"business_domain": "hr",
"sensitivity": "confidential",
"via_roles": ["hr_admin"],
"actions": ["create", "read", "update", "delete"],
"source_system": "servicenow",
"auth_chain_depth": 1,
"via_identity": "uuid-identity-sn-integration-user",
"computed_at": "2026-01-22T15:00:00Z"
}
],
"sync_version": 42,
"last_synced_at": "2026-01-22T15:00:00Z",
"created_at": "2024-06-15T10:00:00Z"
}
}
Notes on relationship types:
OWNED_BY,AUTHENTICATES_TO,HAS_ROLE,GRANTS,APPLIES_TO,RUNS_AS,EXECUTES_ON(narrowed: automation→resource only),CALLS,INVOKES,USES,AUTHENTICATES_AS,TRIGGERS_ON,CREATED_BY: [Implemented] -- see ADR-007 for execution chain edge type definitions
Get Entity Timeline [Implemented]
GET /api/v1/entities/{id}/timeline
Returns events for this entity, sorted chronologically (newest first by default).
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
event_type | string | Filter: role_assigned, role_revoked, owner_assigned, owner_removed, status_changed, etc. |
since | datetime | Events after this timestamp |
until | datetime | Events before this timestamp |
limit | integer | Max results to return (default: 50). No cursor pagination — returns most recent events up to limit. |
Response:
{
"data": [
{
"id": "event-uuid-...",
"timestamp": "2025-09-03T10:00:00Z",
"event_type": "role_assigned",
"actor": {
"actor_id": "user-uuid-...",
"actor_display_name": "bob.chen@corp.com",
"actor_type": "user"
},
"change_details": {
"operation": "Add member to role",
"target_resources": [
{ "id": "uuid-role-itil", "type": "role", "display_name": "itil" }
]
},
"audit_source": {
"source_api": "directoryAudits",
"source_record_id": "audit-abc123"
}
}
]
}
Path Query Endpoints
Path queries expose the execution/authority graph traversal patterns documented in 03-database.md (Patterns 1-3). These map directly to the StorageAdapter methods getExecutionPaths, getAccessibleBy, and queryPaths.
Blast Radius (Entity -> Resources) [Implemented]
GET /api/v1/entities/{id}/blast-radius
Legacy alias:
GET /api/v1/identities/{id}/blast-radiusis still accepted for backward compatibility.
Returns all resources reachable by this entity through its roles, permissions, and execution chains. Accepts entity types: identity, automation, credential, connection. For non-identity entities, the endpoint follows forwarding edges (RUNS_AS, CALLS, INVOKES, USES, AUTHENTICATES_AS) to find the underlying identity and its execution paths. For cross-system identities, includes paths through AUTHENTICATES_TO edges.
This reads from materialized execution_paths on the entity document -- O(1), no traversal needed.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
business_domain | string | Filter paths by resource domain: hr, finance, customer, it_ops, security, engineering |
sensitivity | string | Filter by minimum sensitivity: public, internal, confidential, restricted |
source_system | string | Filter by target system (e.g., servicenow for cross-system paths only) |
Response:
{
"data": {
"entity_id": "uuid-sp-hr-onboarding",
"entity_name": "SN-Integration-Prod",
"entity_type": "identity",
"total_paths": 4,
"paths": [
{
"resource_id": "uuid-res-hr-case",
"resource_name": "hr_case",
"business_domain": "hr",
"sensitivity": "confidential",
"via_roles": ["hr_admin"],
"actions": ["create", "read", "update", "delete"],
"source_system": "servicenow",
"auth_chain_depth": 1,
"via_identity": "uuid-identity-sn-integration-user",
"computed_at": "2026-01-22T15:00:00Z"
}
],
"domain_summary": {
"hr": { "count": 2, "max_sensitivity": "confidential" },
"it_ops": { "count": 2, "max_sensitivity": "internal" }
}
}
}
Accessible By (Resource -> Accessors) [Implemented]
GET /api/v1/resources/{id}/accessible-by
Returns all entities (identities, automations, etc.) that can reach this resource, with the roles and actions they use. Reads from denormalized accessible_by on the resource document.
Response:
{
"data": {
"resource_id": "uuid-res-hr-case",
"resource_name": "hr_case",
"total_accessors": 3,
"accessors": [
{
"accessor_id": "uuid-sp-hr-onboarding",
"accessor_name": "SN-Integration-Prod",
"accessor_type": "identity",
"via_roles": ["hr_admin"],
"actions": ["create", "read", "update", "delete"],
"computed_at": "2026-01-22T15:00:00Z"
}
]
}
}
Cross-System Paths [Implemented]
GET /api/v1/entities/{id}/cross-system-paths
Legacy alias:
GET /api/v1/identities/{id}/cross-system-pathsis still accepted for backward compatibility.
Returns the AUTHENTICATES_TO chain for this entity -- which target-system identities it authenticates as, and what those identities can reach. Accepts entity types: identity, automation, credential, connection. For non-identity entities (e.g., automations), the endpoint follows forwarding edges (RUNS_AS, CALLS, INVOKES, USES, AUTHENTICATES_AS) via BFS (max 5 hops) to find underlying identities with AUTHENTICATES_TO relationships.
Response:
{
"data": {
"entity_id": "uuid-sp-hr-onboarding",
"entity_name": "SN-Integration-Prod",
"entity_type": "identity",
"source_system": "entra_id",
"auth_chains": [
{
"target_id": "uuid-identity-sn-integration-user",
"target_name": "sn-integration-user",
"target_system": "servicenow",
"evidence_references": {
"issuing_system_id": "a1b2c3d4-...",
"issuing_tenant_id": "72f988bf-...",
"target_system_id": "a1b2c3d4-...",
"target_instance_id": "https://corp.service-now.com",
"matching_field": "client_id",
"matching_value": "a1b2c3d4-..."
},
"execution_paths": [
{
"resource_id": "uuid-res-hr-case",
"resource_name": "hr_case",
"business_domain": "hr",
"sensitivity": "confidential",
"via_roles": ["hr_admin"],
"actions": ["create", "read", "update", "delete"],
"source_system": "servicenow",
"computed_at": "2026-01-22T15:00:00Z"
}
]
}
]
}
}
Finding Endpoints
Findings are deterministic detections. See 01-data-model.md for finding types and evidence completeness.
List Findings [Implemented]
GET /api/v1/findings
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
finding_type | string | orphaned_ownership, ownership_degraded, dormant_authority, scope_drift, privilege_justification_gap, unproven_execution, unknown_identity_binding, reachable_sensitive_domain, llm_egress, external_egress, ownership_invalid, ownership_ambiguous, ownership_unknown |
status | string | active, acknowledged, remediated, false_positive |
severity | string | low, medium, high, critical |
entity_id | string | Findings for a specific entity |
source_system | string | Findings for entities from this source system |
detected_after | datetime | Findings detected after this timestamp |
sort | string | Sort field (default: -detected_at) |
cursor | string | Pagination cursor |
limit | integer | Results per page (default: 50) |
Response:
{
"data": [
{
"id": "uuid-finding-...",
"finding_type": "orphaned_ownership",
"severity": "critical",
"status": "active",
"detected_at": "2026-01-15T12:00:00Z",
"entity_id": "uuid-sp-hr-onboarding",
"entity_name": "SN-Integration-Prod",
"entity_type": "identity",
"title": "Orphaned Ownership: SN-Integration-Prod",
"deterministic_explanation": "Service principal 'SN-Integration-Prod' has no active owner at any level. Primary owner Sarah Chen departed 2025-07-15. Secondary owner IT-Automation-Team disbanded 2026-01-15.",
"affected_resource_count": 4,
"evidence_completeness": {
"current_roles": "available",
"role_history": "unavailable_not_enabled",
"execution_evidence": "available",
"ownership_records": "available",
"approval_records": "unavailable_no_access",
"credential_state": "available"
},
"evidence_pack_id": "uuid-pack-..."
}
],
"cursor": { "next": "...", "has_more": false },
"meta": { "total_count": 12 }
}
Get Finding Detail [Implemented]
GET /api/v1/findings/{id}
Returns full finding with evidence_completeness notes and supporting evidence.
Get Evidence Pack [Implemented]
GET /api/v1/findings/{id}/evidence-pack
Returns the sealed evidence pack for a finding.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
format | string | json (default) or markdown (MVP) |
MVP format policy: JSON and Markdown are the priority output formats. PDF is a post-MVP optional extension generated from Markdown/HTML conversion and is not required in v0.2.
Response (JSON format):
{
"data": {
"id": "uuid-pack-...",
"finding_id": "uuid-finding-...",
"sealed_at": "2026-01-15T12:05:00Z",
"schema_version": "1.0",
"integrity_hash": "sha256:a4f8c2e1...",
"content": {
"identity_summary": { "...": "..." },
"cross_system_auth": { "...": "..." },
"authority_snapshot": { "...": "..." },
"ownership_timeline": [ "..." ],
"blast_radius": { "...": "..." },
"temporal_context": { "...": "..." },
"deterministic_explanation": "...",
"remediation": { "...": "..." },
"evidence_completeness": { "...": "..." }
}
}
}
Update Finding Status [Implemented]
PATCH /api/v1/findings/{id}/status
Request:
{
"status": "acknowledged",
"notes": "Assigned to IT-Ops team for review"
}
Valid transitions: active -> acknowledged -> remediated | false_positive. Reversal: acknowledged -> active.
Temporal Endpoints
Temporal queries enable point-in-time analysis and drift detection. See 03-database.md for the hybrid approach (entity_versions + baseline/event replay).
Point-in-Time State [Implemented]
GET /api/v1/entities/{id}/at/{timestamp}
Returns the entity as it existed at the given timestamp. Uses entity_versions if a direct snapshot exists, otherwise falls back to baseline + event replay.
Response: Same schema as GET /api/v1/entities/{id} but with a reconstructed_at field indicating this is a historical view.
Entity Diff [Implemented]
GET /api/v1/entities/{id}/diff?from={t1}&to={t2}
Returns the diff between two points in time for an entity.
Response:
{
"data": {
"entity_id": "uuid-sp-hr-onboarding",
"from": "2025-03-10T00:00:00Z",
"to": "2026-01-22T00:00:00Z",
"changes": {
"relationships_added": [
{ "type": "HAS_ROLE", "target_name": "itil", "added_at": "2025-09-03" },
{ "type": "HAS_ROLE", "target_name": "hr_agent_workspace", "added_at": "2025-09-03" }
],
"relationships_removed": [
{ "type": "HAS_ROLE", "target_name": "sn_incident_write", "removed_at": "2025-09-03" }
],
"ownership_changes": [
{ "owner_name": "Sarah Chen", "change": "status: active -> decayed", "at": "2025-07-15" }
],
"paths_added": [
{ "resource_name": "hr_case", "domain": "hr", "sensitivity": "confidential" }
],
"paths_removed": []
}
}
}
List Events [Planned]
Not yet implemented. Events are currently queryable only per-entity via
GET /entities/{id}/timeline. A standalone events endpoint is planned for cross-entity event querying.
GET /api/v1/events
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
entity_id | string | Events for a specific entity |
event_type | string | Filter by event type (see 03-database.md Event Types) |
actor_id | string | Events by a specific actor |
since | datetime | Events after this timestamp |
until | datetime | Events before this timestamp |
cursor | string | Pagination cursor |
limit | integer | Results per page (default: 100) |
Sync Management Endpoints
List Syncs [Implemented]
GET /api/v1/syncs
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
connector_type | string | Filter by connector: entra_id, servicenow |
status | string | running, completed, failed, partial |
limit | integer | Results per page (default: 20) |
Response:
{
"data": [
{
"id": "uuid-sync-...",
"connector_type": "entra_id",
"sync_mode": "audit_log",
"status": "completed",
"started_at": "2026-01-22T15:00:00Z",
"completed_at": "2026-01-22T15:03:42Z",
"metrics": {
"audit_records_fetched": 147,
"events_created": 89,
"entities_affected": 23,
"paths_recomputed": 18
}
}
]
}
Trigger Sync [Planned]
Not yet implemented. Syncs are currently triggered by the connector CLI submitting a NormalizedGraph via
POST /api/v1/ingest/normalized-graph. A dedicated sync trigger endpoint is planned.
POST /api/v1/syncs
Request:
{
"connector_type": "entra_id",
"sync_mode": "audit_log"
}
Response: 202 Accepted with sync record (status: running).
Get Sync Status [Implemented]
GET /api/v1/syncs/{id}
Returns full sync record with metrics. Poll this endpoint to track running syncs.
Batch Entity Fetch [Implemented]
GET /api/v1/entities/batch?ids={id1},{id2},...
Returns up to 100 entities by ID in a single request. The ids query parameter is comma-separated.
Response:
{
"data": [
{ "...entity 1..." },
{ "...entity 2..." }
]
}
Error codes:
MISSING_IDS(400) --idsquery parameter not providedTOO_MANY_IDS(400) -- more than 100 IDs requested
Entity Version History [Implemented]
GET /api/v1/entities/{id}/versions
Returns the version history for an entity, showing how it changed across sync versions.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page (default: 50) |
Response:
{
"data": [
{
"sync_version": 42,
"snapshot_at": "2026-01-22T15:00:00Z",
"properties": { "...": "..." },
"relationships": [ "..." ],
"execution_paths": [ "..." ]
},
{
"sync_version": 41,
"snapshot_at": "2026-01-15T15:00:00Z",
"properties": { "...": "..." },
"relationships": [ "..." ],
"execution_paths": [ "..." ]
}
]
}
Graph Subgraph [Implemented]
GET /api/v1/graph/subgraph
Returns a subgraph of entities and relationships starting from a seed entity. Supports neighborhood traversal (all directions) and execution flow traversal (follows execution chain edge types).
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
seed_id | string | (required) Starting entity ID |
mode | string | neighborhood (default) or execution_flow |
depth | integer | Traversal depth: 1-3 (default: 2) |
limit | integer | Max nodes returned (default: 100, max: 500) |
relationship_types | string | Comma-separated filter (e.g., CALLS,INVOKES,USES) |
source_system | string | Filter nodes by source system |
Response:
{
"data": {
"nodes": [
{
"_id": "uuid-sp-hr-onboarding",
"entity_type": "identity",
"source_system": "entra_id",
"properties": { "display_name": "SN-Integration-Prod", "...": "..." }
},
{
"_id": "uuid-role-hr-admin",
"entity_type": "role",
"source_system": "servicenow",
"properties": { "display_name": "hr_admin", "...": "..." }
}
],
"edges": [
{
"source_id": "uuid-sp-hr-onboarding",
"target_id": "uuid-role-hr-admin",
"relationship_type": "HAS_ROLE",
"properties": { "granted_at": "2025-03-10" }
}
]
}
}
Error codes:
MISSING_SEED_ID(400) --seed_idquery parameter not providedINVALID_MODE(400) --modeis notneighborhoodorexecution_flow
Execution Chain Endpoints [Implemented]
These endpoints are defined in ADR-008.
List Execution Chains [Implemented]
GET /api/v1/execution-chains
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
egress_category | string | Filter: identity_provider, itsm, monitoring, unknown |
ownership_status | string | Filter: owned, orphaned, degraded |
max_sensitivity | string | Minimum sensitivity: public, internal, confidential, restricted |
blast_radius_domain | string | Filter chains that reach a specific domain |
cursor | string | Pagination cursor |
limit | integer | Results per page (default: 50) |
sort | string | Sort field. Prefix with - for descending. |
Get Execution Chain Detail [Implemented]
GET /api/v1/execution-chains/{id}
Returns full chain with entity_refs, summary, and composition_hash.
Response:
{
"data": {
"id": "uuid-chain-...",
"anchor_entity_id": "uuid-br-autoclose-incidents",
"name": "BR: Auto-close incidents → Entra ID",
"entity_refs": [
{ "entity_id": "uuid-br-autoclose-incidents", "role": "entry_point", "entity_type": "automation" },
{ "entity_id": "uuid-si-rest-utils", "role": "code_component", "entity_type": "automation" },
{ "entity_id": "uuid-conn-graph-api", "role": "outbound_target", "entity_type": "connection" },
{ "entity_id": "uuid-cred-oauth-profile", "role": "auth_credential", "entity_type": "credential" },
{ "entity_id": "uuid-sp-graph-integration", "role": "destination_identity", "entity_type": "identity" }
],
"summary": {
"trigger": "incident.update",
"destination": "graph.microsoft.com",
"egress_category": "identity_provider",
"ownership_status": "owned",
"max_sensitivity": "confidential",
"blast_radius_domains": ["hr", "it_ops"],
"total_roles": 3,
"canonical_permissions": { "reads": ["DataRead"], "writes": ["MetadataWrite"] }
},
"composition_hash": "sha256:c3d4e5f6...",
"first_detected_at": "2026-01-10T10:00:00Z",
"last_seen_at": "2026-01-22T15:00:00Z"
}
}
W1 Posture & Exposure Endpoints [Implemented]
These endpoints serve the W1 (Agentic AI Exposure Discovery & Assessment) product wedge. They compute derived views from the entity graph — no new collections required. See W1 implementation plan for full specification.
Posture Summary [Implemented]
GET /api/v1/posture/summary
Returns 4 execution visibility categories for the homepage posture cards, plus delta since last refresh.
Response:
{
"data": {
"active_autonomous": { "count": 142, "label": "Execution Identities" },
"dormant_authority": { "count": 38, "label": "Dormant Authority Identities" },
"operator_assisted": { "count": 856, "label": "Assisted Workloads" },
"human_triggered": { "count": 2100, "label": "Human-Triggered Workloads" }
},
"delta_since_last_refresh": {
"new_autonomous_identities": 2,
"ownership_invalidations": 3,
"last_sync_at": "2026-02-16T10:42:00Z"
}
}
Counting rules:
- Cards 1-2 count distinct identities (RUNS_AS targets of autonomous workloads), not workloads. Multiple workloads sharing one identity count as 1.
- Cards 3-4 count workload entities by
execution_mode.
Implementation: MongoDB aggregation pipeline on entities collection.
Risk Clusters [Implemented]
GET /api/v1/posture/risk-clusters
Returns compound-condition risk clusters for triage prioritization.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max clusters to return (default: 10) |
Response:
{
"data": [
{
"cluster_key": "sensitive:llm:active:ownership_invalid",
"label": "Sensitive + LLM + Active + Invalid Owner",
"identity_count": 12,
"execution_count_30d": 8400,
"sensitive_domains": ["financial", "identity"],
"ownership_invalid_count": 12,
"priority": "P0"
}
]
}
4 compound dimensions: max_sensitivity_bucket × egress_category × execution_active × ownership_status. identity_count reflects distinct RUNS_AS identity targets, not workloads.
List Exposures [Implemented]
GET /api/v1/exposures
Computes exposures from entity graph. An Exposure is a derived view of one Authority Path (Workload → Identity → Destination → Data Domain). NOT backed by execution_chains collection.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
cluster_key | string | Filter by risk cluster key |
execution_mode | string | Filter: autonomous, operator_assisted, human_triggered |
finding_type | string | Filter by associated finding type |
data_domain | string | Filter by data domain |
q | string | Text search by exposure name |
limit | integer | Results per page (default: 50) |
cursor | string | Pagination cursor |
Computation:
- Query workload entities matching filters
- For each workload, resolve RUNS_AS identity
- If identity resolved: group
execution_paths[]by (destination, data_domain) → one Exposure per path - If identity unknown (no RUNS_AS): produce Exposure with
identity=unknown— every autonomous workload has at least one Exposure row - Enrich with execution stats, ownership state, finding counts, evidence completeness
Exposure ID is deterministic: EXP-{short_hash(tenant_id, workload_id, identity_id, destination, data_domain)}.
Get Exposure Detail [Implemented]
GET /api/v1/exposures/{id}
Returns a single exposure with full detail for 6 UI panels: authority path, standing authority, deterministic linkage proof, ownership breakdown, workload metadata, identity binding.
Response: See W1 implementation plan §4 for full response schema.
Structured Query [Planned]
Not yet implemented. Complex filtering is currently done via query parameters on
GET /api/v1/entities. A structured query DSL endpoint is planned for cross-entity queries.
For complex cross-entity queries that span multiple filters and entity types.
POST /api/v1/query
Request:
{
"entity_type": "identity",
"filters": {
"and": [
{ "field": "properties.status", "op": "eq", "value": "active" },
{ "field": "properties.identity_subtype", "op": "in", "value": ["service_principal", "oauth_app"] },
{ "field": "properties.last_activity_at", "op": "lt", "value": "2025-07-22T00:00:00Z" }
]
},
"with_findings": {
"finding_type": "orphaned_ownership",
"status": "active"
},
"with_paths_to": {
"business_domain": "hr",
"sensitivity": "confidential"
},
"sort": "-properties.last_activity_at",
"limit": 50
}
Operators:
| Operator | Description | Example |
|---|---|---|
eq | Equals | { "field": "status", "op": "eq", "value": "active" } |
ne | Not equals | { "field": "status", "op": "ne", "value": "deleted" } |
in | Value in list | { "field": "identity_subtype", "op": "in", "value": ["service_principal", "oauth_app"] } |
gt / lt | Greater / less than | { "field": "last_activity_at", "op": "lt", "value": "2025-07-22T00:00:00Z" } |
between | Range (inclusive) | { "field": "last_activity_at", "op": "between", "value": ["2025-01-01", "2025-12-31"] } |
exists | Field exists | { "field": "properties.oauth_config", "op": "exists", "value": true } |
Logical operators: and, or (no nesting beyond one level for MVP).
Join-like filters:
with_findings-- only return entities that have matching findingswith_paths_to-- only return identities that have execution paths matching the criteria
Response: Same schema as GET /api/v1/entities list response.
Pagination
All list endpoints use cursor-based pagination. SCIM endpoints use the RFC 9865 cursor extension (see 06-scim-oaa-integration.md).
Request:
GET /api/v1/entities?limit=50&cursor=eyJsYXN0X2lkIjoiLi4uIn0=
Response envelope:
{
"data": [ "..." ],
"cursor": {
"next": "eyJsYXN0X2lkIjoiLi4uIn0=",
"has_more": true
},
"meta": {
"total_count": 342
}
}
cursor.next-- opaque string to pass as?cursor=for next page.nullif no more results.cursor.has_more-- boolean convenience field.meta.total_count-- total matching results (may be approximate for large result sets).- Default page size: 50. Maximum: 200.
Error Responses
All errors follow a consistent format:
{
"error": {
"code": "ENTITY_NOT_FOUND",
"message": "Entity with id 'uuid-...' not found in tenant",
"status": 404,
"details": {}
}
}
Standard error codes:
| HTTP Status | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Malformed request body or query parameters |
| 400 | INVALID_FILTER | Structured query has invalid field or operator |
| 401 | UNAUTHORIZED | Missing or invalid authentication |
| 403 | FORBIDDEN | Authenticated but not authorized for this tenant |
| 404 | ENTITY_NOT_FOUND | Entity does not exist in this tenant |
| 404 | FINDING_NOT_FOUND | Finding does not exist in this tenant |
| 409 | INVALID_STATUS_TRANSITION | Finding status transition not allowed |
| 429 | RATE_LIMITED | Too many requests (see rate limit headers) |
| 500 | INTERNAL_ERROR | Unexpected server error |
Rate limit headers (on all responses):
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1706540000
API Versioning
URL-based versioning: /api/v1/...
When a breaking change is introduced, a new version (/api/v2/...) is created. Previous versions are supported for a minimum of 6 months after deprecation notice.
Non-breaking changes (adding fields to responses, new optional query parameters, new endpoints) do not require version bumps.
MVP Exclusions
The following are explicitly out of scope for the MVP API:
- GraphQL -- REST with structured query covers MVP needs. Evaluate if complex nested graph queries become common.
- Webhooks / Subscriptions -- No real-time push. Consumers poll
GET /api/v1/syncs/{id}for sync completion. - Bulk export -- Use
GET /api/v1/findings/{id}/evidence-packindividually. Bulk export deferred. - Native PDF evidence-pack rendering -- Deferred to a separate post-MVP phase (Markdown/HTML-to-PDF conversion pipeline).
- Real-time streaming -- No WebSocket or SSE endpoints.
Endpoint Summary
| Method | Path | Description | Status |
|---|---|---|---|
| GET | /api/v1/entities | List entities with filtering | [Implemented] |
| GET | /api/v1/entities/batch | Batch fetch entities by IDs | [Implemented] |
| GET | /api/v1/entities/{id} | Get entity with relationships and paths | [Implemented] |
| GET | /api/v1/entities/{id}/timeline | Event history for entity | [Implemented] |
| GET | /api/v1/entities/{id}/at/{timestamp} | Point-in-time entity state | [Implemented] |
| GET | /api/v1/entities/{id}/diff | Diff between two timestamps | [Implemented] |
| GET | /api/v1/entities/{id}/versions | Version history | [Implemented] |
| GET | /api/v1/entities/{id}/blast-radius | Execution paths (materialized) | [Implemented] |
| GET | /api/v1/entities/{id}/cross-system-paths | AUTHENTICATES_TO chains | [Implemented] |
| GET | /api/v1/resources/{id}/accessible-by | Reverse path query | [Implemented] |
| GET | /api/v1/graph/subgraph | Subgraph traversal | [Implemented] |
| GET | /api/v1/findings | List findings with filtering | [Implemented] |
| GET | /api/v1/findings/{id} | Finding detail | [Implemented] |
| GET | /api/v1/findings/{id}/evidence-pack | Sealed evidence pack | [Implemented] |
| PATCH | /api/v1/findings/{id}/status | Update finding status | [Implemented] |
| GET | /api/v1/events | List events with filtering | [Planned] |
| GET | /api/v1/syncs | List sync records | [Implemented] |
| POST | /api/v1/syncs | Trigger manual sync | [Planned] |
| GET | /api/v1/syncs/{id} | Sync status and metrics | [Implemented] |
| POST | /api/v1/query | Structured query | [Planned] |
| GET | /api/v1/execution-chains | List execution chains | [Implemented] |
| GET | /api/v1/execution-chains/{id} | Execution chain detail | [Implemented] |
| GET | /api/v1/posture/summary | W1 posture summary (4 visibility categories) | [Implemented] |
| GET | /api/v1/posture/risk-clusters | W1 compound risk clusters | [Implemented] |
| GET | /api/v1/exposures | W1 exposure list (computed from entity graph) | [Implemented] |
| GET | /api/v1/exposures/{id} | W1 exposure detail (6 panels) | [Implemented] |
For SCIM 2.0 and OAA export endpoints, see 06-scim-oaa-integration.md.