API reference

All endpoints are served at https://kefal.dev. Payloads are JSON. Content-Type is application/json unless noted.

Authentication

Kefal uses two distinct auth models, depending on who's calling:

Tenant isolation Every authenticated endpoint scopes its DB reads and writes by the authenticated user_id. One user cannot see another user's agents, graph, incidents, or billing state. This is enforced at the query level, not at the application layer.

Health

MethodPathAuthDescription
GET/api/v1/healthnoneLiveness check. Returns {"status":"ok"}. Used by uptime monitors.

Enrollment & auth

MethodPathAuthDescription
POST/api/v1/enrollusername + passwordAgent enrollment. Creates an agent record, returns a bearer token.
POST/auth/loginusername + passwordDashboard login. Returns a JWT valid for 24h.
GET/api/v1/auth/meJWTCurrent user profile (username, role, trial end, plan).
POST/api/v1/auth/update-emailJWTUpdate the user's email address.
POST/api/v1/auth/forgot-passwordnoneRequest a password-reset email. Always returns 200 (anti-enumeration).
POST/api/v1/auth/reset-passwordreset tokenConsume a reset token and set a new password.

Enroll example

curl -X POST https://kefal.dev/api/v1/enroll \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"..."}'

# → 200
# {"token":"eyJhbGciOiJIU…","agent_id":"3d30dce8-e6b8-4624-8a51-8d1c430c2f67"}

Ingest

MethodPathAuthDescription
POST/api/v1/ingestagent bearerSubmit one snapshot. Returns 200 on success, 402 if the trial/subscription has expired.

Snapshot schema

{
  "agent_id": "3d30dce8-e6b8-4624-8a51-8d1c430c2f67",
  "timestamp": "2026-04-21T10:00:00Z",
  "hostname": "web-01",
  "os": "linux",
  "kernel": "6.8.0-41-generic",
  "uptime_seconds": 1234567,
  "ip_addresses": ["192.168.1.5", "10.0.0.12"],
  "processes": [
    {"pid": 1234, "name": "nginx",    "user": "www-data", "cpu_percent": 0.2},
    {"pid": 1235, "name": "postgres", "user": "postgres", "cpu_percent": 1.1}
  ],
  "listening_ports": [
    {"port": 80,   "protocol": "tcp", "process": "nginx"},
    {"port": 5432, "protocol": "tcp", "process": "postgres"}
  ],
  "logged_in_users": ["alice"]
}

Graph & topology

MethodPathAuthDescription
GET/api/v1/graphJWTReturns {nodes, edges} in Cytoscape-ready format for the Graph view.
GET/api/v1/topologyJWTCompact list of hosts with counts (services, ports, identities) for the List view.

Incidents

MethodPathAuthDescription
GET/api/v1/incidentsJWTList incidents for the current user. Query params: status, severity, limit, offset.
POST/api/v1/incidents/{id}/acknowledgeJWTMark as acknowledged.
POST/api/v1/incidents/{id}/dismissJWTMark as dismissed.
POST/api/v1/incidents/{id}/remediateJWTTrigger on-demand remediation generation (Mode 2 — bypasses the auto-remediation that runs on incident creation).
POST/api/v1/incidents/{id}/remediation/acceptJWTAccept the proposed remediation (advisory — does not apply it; only updates status).
POST/api/v1/incidents/{id}/remediation/rejectJWTReject the proposed remediation.
POST/api/v1/incidents/{id}/remediation/appliedJWTMark remediation as applied. Kefal will verify on the next snapshot that the invariant is no longer violated.

Invariants (collective defensive intelligence)

MethodPathAuthDescription
GET/api/v1/invariantsJWTList the invariants active for your tenant (32 builtins + any community-contributed ones that survived verification).
POST/api/v1/invariants/contributeJWTSubmit a new invariant. See schema below.
POST/api/v1/invariants/{id}/verifyJWTTrigger adversarial verification on a pending invariant (constructive + red-team LLM prompts).

Contribution schema

{
  "name":           "A short unique name",
  "invariant_type": "structural | temporal | behavioral",
  "predicate": {
    "kind":   "process_in_suspicious_path",
    "params": { "user": "root", "suspicious_paths": ["/tmp"] }
  },
  "severity": "low | medium | high | critical",
  "metadata": {
    "description": "What this invariant detects, in one sentence.",
    "pitch":       "Why SMBs should care."
  }
}

Billing (PayPal)

MethodPathAuthDescription
GET/api/v1/billing/plansJWTList the three plans with prices and limits.
GET/api/v1/billing/statusJWTCurrent plan, trial/active state, next billing date.
POST/api/v1/billing/checkoutJWTStart a PayPal checkout for the requested plan. Returns an approval URL.
POST/api/v1/billing/cancelJWTCancel the active subscription (remains active until period end).
POST/api/v1/billing/webhookPayPal signaturePayPal webhook sink. Verified via PAYPAL_WEBHOOK_ID.

Admin (admin role only)

MethodPathAuthDescription
GET/api/v1/admin/dashboardJWT (admin)Platform-wide analytics: users, agents, snapshots, incidents per day.
GET/api/v1/admin/usersJWT (admin)List all users with plan + activity state.
GET/api/v1/admin/users/{user_id}JWT (admin)Detail view of one user — agents, recent incidents, billing.

Status codes