Skip to main content

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_findings filter: [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:

ParameterTypeDescriptionStatus
entity_typestringFilter by type: identity, workload, connection, credential, owner, role, permission, resource, execution_evidence (deprecated alias: automation accepted as workload)[Implemented]
source_systemstringFilter by source: entra_id, servicenow, github, aws[Implemented]
statusstringFilter by status: active, disabled, deleted, departed, disbanded[Implemented]
identity_subtypestringFilter identities by subtype: service_principal, oauth_app, machine_account, integration_user[Implemented]
workload_subtypestringFilter workloads by subtype: business_rule, script_include, flow_designer_flow, scheduled_job, event_script, transform_map (deprecated alias: automation_subtype)[Planned]
connection_subtypestringFilter connections by subtype: rest_message, rest_method, soap_message, http_connection[Planned]
credential_subtypestringFilter credentials by subtype: oauth_provider, oauth_profile, api_key, certificate, client_secret[Planned]
egress_categorystringFilter by egress: identity_provider, itsm, monitoring, unknown[Implemented]
ownership_statusstringFilter: owned, orphaned, degraded[Implemented]
risk_groupstringFilter by risk grouping[Implemented]
execution_modestringFilter: autonomous, operator_assisted, human_triggered, unknown[Implemented]
security_relevancestringFilter: active_external, dormant_authority, internal_inventory[Implemented]
qstringText search across entity names[Implemented]
has_findingsbooleanOnly return entities with active findings[Planned]
cursorstringPagination cursor from previous response[Implemented]
limitintegerResults per page (default: 50, max: 200)[Implemented]
sortstringSort 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 relationships arrays (same shape as detail). The StorageAdapter returns complete EntityDoc objects.
  • entity_type for workload, connection, and execution_evidence is 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:

ParameterTypeDescription
event_typestringFilter: role_assigned, role_revoked, owner_assigned, owner_removed, status_changed, etc.
sincedatetimeEvents after this timestamp
untildatetimeEvents before this timestamp
limitintegerMax 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-radius is 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:

ParameterTypeDescription
business_domainstringFilter paths by resource domain: hr, finance, customer, it_ops, security, engineering
sensitivitystringFilter by minimum sensitivity: public, internal, confidential, restricted
source_systemstringFilter 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-paths is 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:

ParameterTypeDescription
finding_typestringorphaned_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
statusstringactive, acknowledged, remediated, false_positive
severitystringlow, medium, high, critical
entity_idstringFindings for a specific entity
source_systemstringFindings for entities from this source system
detected_afterdatetimeFindings detected after this timestamp
sortstringSort field (default: -detected_at)
cursorstringPagination cursor
limitintegerResults 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:

ParameterTypeDescription
formatstringjson (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:

ParameterTypeDescription
entity_idstringEvents for a specific entity
event_typestringFilter by event type (see 03-database.md Event Types)
actor_idstringEvents by a specific actor
sincedatetimeEvents after this timestamp
untildatetimeEvents before this timestamp
cursorstringPagination cursor
limitintegerResults per page (default: 100)

Sync Management Endpoints

List Syncs [Implemented]

GET /api/v1/syncs

Query Parameters:

ParameterTypeDescription
connector_typestringFilter by connector: entra_id, servicenow
statusstringrunning, completed, failed, partial
limitintegerResults 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) -- ids query parameter not provided
  • TOO_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:

ParameterTypeDescription
limitintegerResults 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:

ParameterTypeDescription
seed_idstring(required) Starting entity ID
modestringneighborhood (default) or execution_flow
depthintegerTraversal depth: 1-3 (default: 2)
limitintegerMax nodes returned (default: 100, max: 500)
relationship_typesstringComma-separated filter (e.g., CALLS,INVOKES,USES)
source_systemstringFilter 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_id query parameter not provided
  • INVALID_MODE (400) -- mode is not neighborhood or execution_flow

Execution Chain Endpoints [Implemented]

These endpoints are defined in ADR-008.

List Execution Chains [Implemented]

GET /api/v1/execution-chains

Query Parameters:

ParameterTypeDescription
egress_categorystringFilter: identity_provider, itsm, monitoring, unknown
ownership_statusstringFilter: owned, orphaned, degraded
max_sensitivitystringMinimum sensitivity: public, internal, confidential, restricted
blast_radius_domainstringFilter chains that reach a specific domain
cursorstringPagination cursor
limitintegerResults per page (default: 50)
sortstringSort 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:

ParameterTypeDescription
limitintegerMax 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:

ParameterTypeDescription
cluster_keystringFilter by risk cluster key
execution_modestringFilter: autonomous, operator_assisted, human_triggered
finding_typestringFilter by associated finding type
data_domainstringFilter by data domain
qstringText search by exposure name
limitintegerResults per page (default: 50)
cursorstringPagination cursor

Computation:

  1. Query workload entities matching filters
  2. For each workload, resolve RUNS_AS identity
  3. If identity resolved: group execution_paths[] by (destination, data_domain) → one Exposure per path
  4. If identity unknown (no RUNS_AS): produce Exposure with identity=unknown — every autonomous workload has at least one Exposure row
  5. 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:

OperatorDescriptionExample
eqEquals{ "field": "status", "op": "eq", "value": "active" }
neNot equals{ "field": "status", "op": "ne", "value": "deleted" }
inValue in list{ "field": "identity_subtype", "op": "in", "value": ["service_principal", "oauth_app"] }
gt / ltGreater / less than{ "field": "last_activity_at", "op": "lt", "value": "2025-07-22T00:00:00Z" }
betweenRange (inclusive){ "field": "last_activity_at", "op": "between", "value": ["2025-01-01", "2025-12-31"] }
existsField 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 findings
  • with_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. null if 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 StatusCodeDescription
400INVALID_REQUESTMalformed request body or query parameters
400INVALID_FILTERStructured query has invalid field or operator
401UNAUTHORIZEDMissing or invalid authentication
403FORBIDDENAuthenticated but not authorized for this tenant
404ENTITY_NOT_FOUNDEntity does not exist in this tenant
404FINDING_NOT_FOUNDFinding does not exist in this tenant
409INVALID_STATUS_TRANSITIONFinding status transition not allowed
429RATE_LIMITEDToo many requests (see rate limit headers)
500INTERNAL_ERRORUnexpected 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-pack individually. 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

MethodPathDescriptionStatus
GET/api/v1/entitiesList entities with filtering[Implemented]
GET/api/v1/entities/batchBatch fetch entities by IDs[Implemented]
GET/api/v1/entities/{id}Get entity with relationships and paths[Implemented]
GET/api/v1/entities/{id}/timelineEvent history for entity[Implemented]
GET/api/v1/entities/{id}/at/{timestamp}Point-in-time entity state[Implemented]
GET/api/v1/entities/{id}/diffDiff between two timestamps[Implemented]
GET/api/v1/entities/{id}/versionsVersion history[Implemented]
GET/api/v1/entities/{id}/blast-radiusExecution paths (materialized)[Implemented]
GET/api/v1/entities/{id}/cross-system-pathsAUTHENTICATES_TO chains[Implemented]
GET/api/v1/resources/{id}/accessible-byReverse path query[Implemented]
GET/api/v1/graph/subgraphSubgraph traversal[Implemented]
GET/api/v1/findingsList findings with filtering[Implemented]
GET/api/v1/findings/{id}Finding detail[Implemented]
GET/api/v1/findings/{id}/evidence-packSealed evidence pack[Implemented]
PATCH/api/v1/findings/{id}/statusUpdate finding status[Implemented]
GET/api/v1/eventsList events with filtering[Planned]
GET/api/v1/syncsList sync records[Implemented]
POST/api/v1/syncsTrigger manual sync[Planned]
GET/api/v1/syncs/{id}Sync status and metrics[Implemented]
POST/api/v1/queryStructured query[Planned]
GET/api/v1/execution-chainsList execution chains[Implemented]
GET/api/v1/execution-chains/{id}Execution chain detail[Implemented]
GET/api/v1/posture/summaryW1 posture summary (4 visibility categories)[Implemented]
GET/api/v1/posture/risk-clustersW1 compound risk clusters[Implemented]
GET/api/v1/exposuresW1 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.