STACK records security events when a detector reports suspicious activity. Use this API to list, inspect, and resolve those events. All endpoints require a non-agent operator principal.
Team-member sessions and agent-bearing contexts receive 403 Forbidden. Use the operator API key, a dashboard session, or a non-agent OAuth token.
Retrieve unresolved security events for your organization, with optional filtering by agent and pagination.
# List unresolved events (default)
curl "https://api.getstack.run/v1/security-events?page=1&limit=50" \
-H "Authorization: Bearer sk_live_your_key"
# Filter by agent
curl "https://api.getstack.run/v1/security-events?agent_id=agt_xyz&limit=20" \
-H "Authorization: Bearer sk_live_your_key"{
"events": [
{
"id": "sev_abc123",
"operator_id": "op_xyz",
"agent_id": "agt_xyz",
"passport_jti": "jti_abc",
"signal_type": "credential_burst",
"severity": "warning",
"message": "Agent retrieved 15 credentials within 30 seconds",
"metadata": {
"credential_count": 15,
"time_window_seconds": 30
},
"resolved": false,
"resolved_at": null,
"created_at": "2026-04-15T10:05:00Z"
},
{
"id": "sev_def456",
"operator_id": "op_xyz",
"agent_id": "agt_abc",
"passport_jti": "jti_def",
"signal_type": "credential_outside_scope",
"severity": "critical",
"message": "Proxy request for notion not in passport scope",
"metadata": {
"intent_services": ["slack", "github"],
"granted_providers": ["slack", "github"]
},
"resolved": false,
"resolved_at": null,
"created_at": "2026-04-15T09:30:00Z"
}
],
"unresolved_count": 7,
"page": 1,
"limit": 50
}The unresolved_count field reflects the total number of unresolved events across your entire organization, regardless of pagination or agent filter. Use this to power badge counts in your UI.
Retrieve full details for a specific security event by ID.
curl https://api.getstack.run/v1/security-events/sev_abc123 \
-H "Authorization: Bearer sk_live_your_key"Returns 404 if the event does not exist or belongs to a different operator.
These are the principal real-time signals exposed by this API. The detector catalog also includes review-time and substrate-aware signals; see /docs/concepts/detectors for the complete list.
An agent attempted to access a credential or proxy a request for a service that is not listed in the passport's scope. STACK blocks the request in every accountability mode; logged mode changes the event severity, not the access decision.
An agent attempted to access a credential or use the proxy after the passport has been checked out. This is always blocked regardless of accountability mode.
An agent crossed the credential-retrieval threshold for one provider inside the configured time window, which may indicate credential exfiltration.
A passport delegation was created without a matching intent declaration.
A parent attempted to delegate work to an agent with a weaker accountability mode. STACK records the critical event and refuses the delegation.
An agent failed to report a checkpoint within the expected interval.
A passport expired without ever being checked out, which may indicate an agent that abandoned a task without proper cleanup.
An agent is systematically requesting passports with increasingly broader scopes, potentially probing for access.
An agent accessed credentials during a passport session but did not report them in the checkout. This signal is generated by comparing Redis-tracked credential access against the checkout report.
Every security event has one of three severity levels:
When a critical-severity event is recorded, STACK checks the agent's on_critical configuration. If set to block, the agent is automatically blocked from receiving new passports. The agent's passport_blocked flag is set to true with a reason message.
Agents with eval_traffic: true (set viaPATCH /v1/agents/:id) run benchmark or eval workloads. Detection stays fully on and every event still records and appears in this API, but events at severity warning or below insert already resolved (resolved_by: "auto:eval_traffic") and skip notification dispatch. Critical events stay completely live - recorded unresolved, notified, and honored by automatic agent blocking. The expensive LLM classification layer (L3) of the prompt-injection chain is skipped for these agents; the regex and normalization layers still produce verdicts. Distinct fromis_synthetic. Synthetic events are hidden by default. Pass include_synthetic=true to include them. Both flags are operator-set only: agent contexts cannot PATCH them and partner grants are explicitly denied both.
Mark a security event as resolved after investigation. The resolve endpoint takes no request body.
curl -X POST https://api.getstack.run/v1/security-events/sev_abc123/resolve \
-H "Authorization: Bearer sk_live_your_key"{
"resolved": true,
"id": "sev_abc123"
}Returns 404 if the event does not exist or belongs to a different operator.
Once resolved, an event cannot be re-opened. If the same anomaly recurs, STACK generates a new event automatically.
Scope-expanding actions require a human-approved governance approval when called with a Bearer credential (operator key or OAuth token - including agentic MCP sessions): a detector config write whose delta loosens detection, detector config reset, a review decision of approved, and agent unblock. Resolving a security event is deliberately NOT gated, nor is any read or any tightening change. Dashboard sessions never step up - the human in the dashboard is the approval.
A gated call without the header does not execute. It creates a pending approval and returns 403 GOVERNANCE_APPROVAL_REQUIRED whose message carries the approval id (gvr_...) and the dashboard URL. A notification is pushed to the operator. After a human approves at/governance/approvals, retry the SAME call with the approval id in the X-Governance-Approval header.
# 1. Gated call from a Bearer credential -> 403 with an approval id
curl -X POST https://api.getstack.run/v1/agents/agt_xyz/unblock \
-H "Authorization: Bearer sk_live_your_key"
# -> 403 { "error": { "code": "GOVERNANCE_APPROVAL_REQUIRED",
# "message": "... approve request gvr_abc123 at https://getstack.run/governance/approvals,
# then retry the same call with header X-Governance-Approval: gvr_abc123" } }
# 2. Human approves in the dashboard, then retry the SAME call:
curl -X POST https://api.getstack.run/v1/agents/agt_xyz/unblock \
-H "Authorization: Bearer sk_live_your_key" \
-H "X-Governance-Approval: gvr_abc123"The approval binds tightly: same operator, same action and resource, and an exact hash of the request payload - a retried body that differs from what the human saw is refused. Approvals are single-use and expire after 15 minutes.
List this operator's governance approvals, pending first. Statuses:pending, approved,rejected, consumed,expired. Agent-authenticated contexts are denied.
curl https://api.getstack.run/v1/governance/approvals \
-H "Authorization: Bearer sk_live_your_key"{
"approvals": [
{
"id": "gvr_abc123",
"action": "agent_unblock",
"resource_id": "agt_xyz",
"summary": "Unblock agent agt_xyz for passport issuance",
"requested_via": "oauth",
"status": "pending",
"expires_at": "2026-07-19T10:45:00.000Z",
"created_at": "2026-07-19T10:30:00.000Z",
"decided_at": null
}
]
}Approve or reject a pending approval. Body:{ "decision": "approve" } or{ "decision": "reject" }. This endpoint accepts ONLY a signed-in dashboard session - a Bearer credential can never approve its own elevation. In practice you decide from the dashboard approvals page, not curl.
Every detector fire publishes a notification event whose type issecurity.<signal_type> - for examplesecurity.credential_outside_scope orsecurity.intent_deviation. Subscribe at per-detector granularity and gate by minimum severity. Both filters apply - the channel receives the notification only if it is subscribed to the event AND the fire severity meets the channel's min_severity floor.
# Page oncall on hard scope violations and delegation downgrades only
curl -X POST https://api.getstack.run/v1/notifications/channels \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"events": [
"security.credential_outside_scope",
"security.delegation_downgrade"
],
"min_severity": "critical",
"destination_ids": ["ndst_pagerduty", "ndst_oncall_email"]
}'The notification payload carries signal_type,agent_id, passport_jti,event_id, and the message, so webhook handlers can switch on signal_type - e.g. auto- revoke all passports for an agent when credential_outside_scopefires, just log intent_deviation.