STACK
MENU
DOCS / API REFERENCE / SECURITY EVENTS

Security Events API

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.

List Security Events

Retrieve unresolved security events for your organization, with optional filtering by agent and pagination.

bash
# 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"

Query Parameters

  • agent_id (string, optional) - filter events by a specific agent ID
  • page (integer, optional) - page number, default 1 (1-indexed)
  • limit (integer, optional) - events per page, default 50, max 500
  • include_synthetic (boolean, optional) - include synthetic-agent events that are hidden by default

Response

json
{
  "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.

Get a Security Event

Retrieve full details for a specific security event by ID.

bash
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.

Signal Types

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.

credential_outside_scope

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.

  • Severity: critical (enforced mode) or warning (logged mode)
  • Metadata: intent_services, granted_providers

credential_after_checkout

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.

  • Severity: critical
  • Metadata: service name, passport JTI

credential_burst

An agent crossed the credential-retrieval threshold for one provider inside the configured time window, which may indicate credential exfiltration.

  • Severity: critical
  • Metadata: credential_count, time_window_seconds

delegation_without_intent

A passport delegation was created without a matching intent declaration.

  • Severity: critical in enforced mode; warning otherwise

delegation_downgrade

A parent attempted to delegate work to an agent with a weaker accountability mode. STACK records the critical event and refuses the delegation.

  • Severity: critical

checkpoint_silence

An agent failed to report a checkpoint within the expected interval.

  • Severity: warning

expired_no_checkout

A passport expired without ever being checked out, which may indicate an agent that abandoned a task without proper cleanup.

  • Severity: info

scope_escalation_pattern

An agent is systematically requesting passports with increasingly broader scopes, potentially probing for access.

  • Severity: warning

credential_unreported

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.

  • Severity: warning
  • Metadata: accessed services, reported services

Severity Levels

Every security event has one of three severity levels:

  • info - informational, no immediate action required (e.g., expired_no_checkout)
  • warning - suspicious pattern detected, should be reviewed (e.g., credential_burst, credential_unreported)
  • critical - active security threat, requires immediate investigation (e.g., credential_outside_scope in enforced mode, credential_after_checkout)

Automatic Agent Blocking

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.

Eval Traffic

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.

Resolve a Security Event

Mark a security event as resolved after investigation. The resolve endpoint takes no request body.

bash
curl -X POST https://api.getstack.run/v1/security-events/sev_abc123/resolve \
  -H "Authorization: Bearer sk_live_your_key"
json
{
  "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.

Governance Approvals (Human Step-Up)

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.

bash
# 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.

GET /v1/governance/approvals

List this operator's governance approvals, pending first. Statuses:pending, approved,rejected, consumed,expired. Agent-authenticated contexts are denied.

bash
curl https://api.getstack.run/v1/governance/approvals \
  -H "Authorization: Bearer sk_live_your_key"
json
{
  "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
    }
  ]
}

POST /v1/governance/approvals/:id/decide

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.

Automated Response

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.

bash
# 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.

Operational guidance

  • Subscribe to at least security.credential_outside_scope and security.delegation_downgrade at severity=critical before going to production
  • Use webhook delivery for automated remediation; email/SMS for human-in-the-loop operators
  • Review and resolve open events regularly - check unresolved_count from the dashboard
  • Configure on_critical: "block" when a critical event must stop new Passport issuance
  • Use enforced accountability mode on passports so auto-revoke can fire on critical signals
stack | Docs