Skip to content

Audit & stats endpoints

Read-only endpoints the SPA hits for the dashboard. All tenant-scoped.

GET /audit?limit=N

Recent decisions, newest first. limit defaults to 100.

GET /events (SSE)

text/event-stream. One data: line per new decision. Caddy is configured with flush_interval -1 so chunks aren't buffered.

GET /stats?window=N

Aggregates over the last N seconds. Default 3600.

json
{
  "window_seconds":     3600,
  "total_decisions":    153,
  "by_action":          { "allow": 142, "redact": 8, "deny": 3 },
  "top_rules":          [ { "key": "redact_secrets_on_egress", "count": 8 }, ... ],
  "top_agents":         [ { "key": "support-bot-v2", "count": 1000, "ip": "10.0.3.42", ... }, ... ],
  "top_blocked_agents": [ { "key": "...", "count": 3, ... }, ... ],
  "top_blocked_hosts":  [ { "key": "...", "count": 2 }, ... ]
}

top_* fields are lists of {key, count} (agent lists carry network identity too), not maps. There are more rollups than shown (shadow agents, anomaly/approval counts, token/cost totals, principal counts) — the dashboard consumes the ones it needs.

GET /vocabulary

Distinct values observed in the audit data — used to populate filter autocomplete on Live Traffic.

json
{
  "agents":  ["support-bot-v2", "qa_eval_loop", ...],
  "actions": ["http_request", "tool_call", "llm_prompt"],
  "hosts":   ["api.anthropic.com", "api.openai.com", ...],
  "tools":   ["shell.exec", "fs.read", ...],
  "models":  ["claude-sonnet-4-6", "gpt-5-mini", ...]
}

GET /audit/export?format=csv|json&window=N

Same data as /audit but as a download with Content-Disposition set (capped at 10,000 rows). format=csv is what the dashboard's Export button uses; format=json returns the same rows as JSON.

GET /principals/summary

Per-principal-type counts (people vs. agents vs. unknown):

json
{
  "user":    { "count": 12, "names": [...] },
  "agent":   { "count":  8, "names": [...] },
  "unknown": { "count":  0, "names": [] }
}

Documentation for kilasec — the AI Agent Firewall.