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:
{
"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": {
"name": "support-bot-v2",
"hostname": "ops-mbp",
"mac": "3c:22:fb:a1:b2:c3",
"principal_type": "agent",
"labels": { "hostname_source": "reverse_dns" }
}
}
},
"decision": {
"action": "redact",
"matched_rule": "redact_pii_on_egress",
"reason": "PII detected in egress payload",
"redacted_fields": ["body.card_number"],
"duration_ms": 1.83
}
}
]
}Response:
{ "ok": true, "ingested": 1 }Notes:
- Events with duplicate
idare upserted (idempotent — collector can retry safely on network blip). request.argsshould not containAuthorization/x-api-keyetc. The collector's_classify_requestscrubs these before shipping; cloud will accept them but it's wasted bandwidth and a security smell.tsis unix seconds, float.
POST /v1/ingest/usage
Periodic token / cost rollups for cost attribution.
Request:
{
"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:
{ "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:
{
"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:
{ "ok": true, "synced": 1 }Keys in the agents object are the agent names; values are profile dicts. Upsert on (tenant_id, name).
Suggested ingest cadence
- Events: as they happen, batched. The collector buffers up to 50 events or 1 second, whichever comes first, then POSTs.
- Usage: every 60 seconds, batched.
- Agents: every 5 minutes — this is reference data, not real-time.
Errors
400— invalid JSON / shape. The error detail will be Pydantic's diagnostic.401— bad credential.429— rate limit (default 200 req/s/tenant, burst 500). Back off and retry.503— store is down. Retry with exponential backoff.
→ See audit & stats for what comes back out.