Skip to content

Ingest endpoints

Collector → cloud uploads. Authenticated via Authorization: Bearer <collector_or_tenant_token>. Browsers should never hit these — they're for the on-prem collector's uplink.py.

POST /v1/ingest/events

Upload a batch of decision events.

Request:

json
{
  "events": [
    {
      "id": "ev_W9zQoQ3bk",
      "ts": 1715520000.12,
      "status": "redact",
      "request": {
        "id": "req_k753ucn5",
        "agent": "support-bot-v2",
        "action": "http_request",
        "destination": { "host": "api.anthropic.com", "ip": "160.79.104.10", "port": 443 },
        "args": { "method": "POST", "path": "/v1/messages" },
        "metadata": {
          "source_ip": "10.0.3.42",
          "user_agent": "anthropic-sdk-python/0.30",
          "url": "https://api.anthropic.com/v1/messages",
          "method": "POST",
          "identity": {
            "display_name": "support-bot-v2",
            "hostname": "ops-mbp",
            "principal_type": "agent",
            "agent_source": "user_agent"
          }
        }
      },
      "decision": {
        "action": "redact",
        "matched_rule": "redact_secrets_on_egress",
        "reason": "credential detected in outbound payload — masking before send",
        "matched_paths": ["messages[0].content"]
      }
    }
  ]
}

Response:

json
{ "ok": true, "ingested": 1 }

Notes:

  • Events with duplicate id are upserted (idempotent — collector can retry safely on network blip).
  • request.args should not contain Authorization / x-api-key etc. The collector scrubs these before shipping; cloud will accept them but it's wasted bandwidth and a security smell.
  • ts is unix seconds, float.

POST /v1/ingest/usage

Periodic token / cost rollups for cost attribution.

Request:

json
{
  "records": [
    {
      "ts": 1715520000.0,
      "agent": "support-bot-v2",
      "model": "claude-sonnet-4-6",
      "provider": "anthropic",
      "prompt_tokens": 1820,
      "completion_tokens": 642,
      "cache_read_tokens": 0,
      "cache_write_tokens": 0,
      "cost_usd": 0.018,
      "flow_id": "flow_xxx",
      "identity": { ... }
    }
  ]
}

Response:

json
{ "ok": true, "ingested": 1 }

These feed the Dashboard's Spend KPI and the (planned) Usage & Cost page. The collector emits one row per LLM call when token data is available.

POST /v1/ingest/agents

Agent-profile sync — for each agent the collector has seen, its baseline (hosts touched, tools used, models invoked, principal type, last identity). The cloud aggregates and serves this for the Agents page when there isn't enough fresh /v1/audit data to derive the profile.

Request:

json
{
  "agents": {
    "support-bot-v2": {
      "principal_type": "agent",
      "display_name": "support-bot-v2",
      "first_seen": 1714e9,
      "last_seen": 1715e9,
      "total_requests": 12480,
      "hosts": ["api.anthropic.com", "hooks.slack.com"],
      "tools": ["slack.post"],
      "models": ["claude-sonnet-4-6"],
      "actions": ["http_request", "llm_prompt"],
      "identity": { ... }
    }
  }
}

Response:

json
{ "ok": true, "synced": 1 }

Keys in the agents object are the agent names; values are profile dicts. Upsert on (tenant_id, name).

Ingest cadence

The collector's push loop runs on two cadences:

  • Fast tick (default 2s) — flushes security events: denies, redactions, approval outcomes, anomalies, invalid agent tokens, and config-tamper notes. While an approval is pending, the approval queue also relays on this tick (and immediately on creation), so a require_approval hold reaches the dashboard in well under a second.
  • Full tick (sync_interval, default 10s) — everything else: routine traffic events, usage records, agent profiles, config pulls, and the metrics heartbeat.

Each push drains its buffer (up to 50 batches of 100 per tick), so sustained throughput is not capped at one batch per tick. If the collector still can't keep up, routine traffic is shed first — security events last — and every shed event increments events_dropped_total / usage_dropped_total in the collector's metrics, surfaced as a warning on the Collectors page. Overload is visible, never silent.

Session rollup (traffic events)

Routine clean-allow traffic is rolled up at the collector before shipping (Palo-Alto-style session logging): repeated identical requests — same agent, source IP, destination host, model/tool, action, verdict, and matched rule — inside a window (default 30s, AGENTFW_AGG_WINDOW env; 0 disables) merge into one event carrying:

json
{ "count": 19, "first_ts": 1789245021.1, "ts": 1789245049.8 }

ts is the last request of the group, first_ts the first. Events never rolled up (always one row per request): any enforced verdict (deny / redact / approval), anomalies, invalid agent tokens, LLM completions (llm_prompt — each carries a flow_id joined to a usage record for cost), and any request whose cloud-bound payload trips a content scanner (PII, secret, prompt injection, dangerous action, bulk egress) — so per-request discovery flags stay exact. The cloud stores count as event_count and weights request KPIs by it; the collector's local JSONL audit log keeps every raw request regardless.

Errors

  • 400 — invalid JSON / shape. The error detail will be Pydantic's diagnostic.
  • 401 — bad credential.
  • 503 — store is down. Retry with exponential backoff.

→ See audit & stats for what comes back out.

Documentation for kilasec — the AI Agent Firewall.