API reference
The kilasec cloud backend (cloud.app) speaks JSON over HTTPS. This page is the high-level map; each endpoint group has its own page with shapes and examples.
Base URL
https://kilasec.com/apiCaddy strips the /api prefix and proxies to cloud.app on localhost:8000. In dev, Vite proxies /api → http://localhost:8000 so the same URLs work locally.
Authentication
Three credential mechanisms; cloud-side get_tenant_id() checks a token first (Authorization: Bearer, then X-Api-Token) and falls back to the session cookie when no token is present.
Authorization: Bearer agt_<tenant-or-collector-token>
# or
X-Api-Token: agt_<tenant-or-collector-token>
# or (browser)
Cookie: <session cookie, set at sign-in>Most endpoints require a tenant. A handful are pre-auth (/healthz, /auth/login, /auth/signup, /auth/invite/{code}, the marketing beta-signup). The credential resolves to a tenant_id and every read / write is scoped to that tenant.
Token scopes
The workspace API token is created and rotated on API Access (see API Access & Connect Claude). A rotated token carries one or more scopes:
| Scope | Unlocks |
|---|---|
read | every GET except the few that return a live credential, plus side-effect-free checks (collector health check, support assist) |
policy | rules, profiles, agent roles, signature groups, block notice, scope, dry-run |
connector | event/usage/agent ingest, directory and User-ID sync, identity upsert |
A token with no scopes (any token issued before scopes existed, or one rotated with nothing ticked) has full access. Routes with real blast radius — the CA, collector and device lifecycle, the enrollment secret, retention, pricing, SSO config, approval decisions — are admin-session only and answer 403 to any token. A scoped token calling a route outside its scopes also gets 403, naming the missing scope.
Expiring API keys
Besides the one workspace token, admins can mint named, expiring keys on API Access → Connect Claude → Advanced (troubleshoot = read, configure = read + policy; expiry 1 h, 24 h or 7 d). They are used exactly like the token (Authorization: Bearer agt_…). An expired key returns 401 api key expired; a revoked one returns 401 invalid token. Actions taken with a key are audited as key:<name>.
MCP server
POST https://kilasec.com/api/mcp is a Model Context Protocol server (Streamable HTTP, JSON responses) for AI assistants. It accepts a bearer token only — never a session cookie or a collector token. Without one it returns 401 with a WWW-Authenticate header pointing at the OAuth metadata (/.well-known/oauth-protected-resource), which is how Claude starts the browser sign-in. Each tool calls the REST API above with the caller's own credential, so the same scope checks apply. Setup: Connect Claude.
Endpoint groups
| Group | Page | Purpose |
|---|---|---|
| Ingest | /reference/ingest | Collector → cloud event/usage/agent uploads |
| Audit & stats | /reference/audit | Dashboard reads — events, stats, vocabulary |
| Policy | /reference/policy | Read and write policy rules |
| Collectors | /reference/collectors | Enroll, list, restart, rebuild, revoke |
| Identity | /reference/identities | Identity map, who-is, agent tokens, flow attribution |
| DHCP leases | /reference/leases | Planned — not yet implemented |
Quick reference (the bits everyone needs)
Health
curl https://kilasec.com/api/healthz{ "ok": true, "service": "agentfw-cloud", "db": "ok", "collectors": 3, "collectors_online": 2 }No auth required. Returns 200 when healthy, 503 when the database probe fails (so an uptime monitor actually trips).
Recent decisions
curl -H "Authorization: Bearer $TOKEN" \
"https://kilasec.com/api/audit?limit=10"Returns the most recent N decision events for this tenant, newest first. See the audit endpoint page for the full event shape.
Live tail (SSE)
curl -N -H "Authorization: Bearer $TOKEN" \
https://kilasec.com/api/eventsServer-Sent Events stream. Each data: line is one decision JSON, identical in shape to the /audit rows. Caddy is configured with flush_interval -1 so the stream isn't buffered.
Push events (from a collector)
curl -X POST -H "Authorization: Bearer $COLLECTOR_TOKEN" \
-H "Content-Type: application/json" \
--data '{"events":[{...}]}' \
https://kilasec.com/api/v1/ingest/events{ "ok": true, "ingested": 1 }The collector's uplink.py is what calls this in production. For synthetic testing, see Generate test traffic.
Errors
Standard HTTP. All errors come back as JSON {"detail": "..."}:
| Status | Meaning |
|---|---|
| 400 | malformed body (FastAPI Pydantic validation) |
| 401 | no credential, credential not recognised, or API key expired |
| 403 | CSRF middleware blocked a state-changing call without the X-Kilasec-CSRF header, or the token's scopes don't cover the route |
| 404 | tenant-scoped resource doesn't exist for this tenant |
| 429 | rate limit on an auth/enrollment route |
| 503 | database degraded (also what /healthz returns when the DB probe fails) |
CSRF
State-changing requests from the browser must carry X-Kilasec-CSRF: 1. The SPA does this automatically; if you're testing with curl from your own browser session cookie, add the header manually.
Collector tokens are exempt — they hit /v1/ingest/* with Authorization: Bearer only and bypass CSRF (which is correct: CSRF is a same-origin browser concern, not a server-to-server one).
Rate limits
The authentication and enrollment endpoints are rate-limited per client. Collector clients batch anyway — the uplink POSTs events in batches (default 100), not one HTTP call per decision — so you won't hit a limit in normal use.
Backward compatibility
API versions live in the path: /v1/…. New fields are additive. Renames go through a deprecation cycle of at least one minor version with both names valid; check the changelog before upgrading.