Operator API reference

REST + Server‑Sent Events over the Go API in `backend/api`. All protected endpoints require Authorization: Bearer <jwt>. A 401 means re‑login.

Base URL in the lab: http://192.168.10.200:8080. In npm run dev, Vite proxies /api and /events from :5173 to :8080.

Auth

Method Path Body Returns
POST /api/login { "username": "...", "password": "..." } `{ "token": "...", "username": "...", "role": "viewer\ analyst\ responder\ admin" }`
GET /api/me { "username": "...", "role": "..." }
POST /api/change-password { "old": "...", "new": "..." } { "status": "ok" }

/api/login is unauthenticated; everything else requires the bearer token.

Read‑only (viewer+)

Method Path Returns
GET /api/incidents [ { id, attacker_mac, kinds, claimed_ips, sensor_ids, detection_count, distinct_ips, distinct_sensors, confidence, severity, status, first_seen, last_seen, mitre } ]
GET /api/detections [ { ts, site, vlan, sensor_id, rule_id, severity, confidence, eth_src, sha, spa, tpa, gratuitous, note, pcap_ref } ]
GET /api/bindings [ { site, vlan, ip, mac, switch_port, is_protected, state, first_seen, last_seen } ]
GET /api/queue proposed bindings awaiting approval
GET /api/mitigations [ { ts, incident_id, target_mac, target_ip, vlan, level, actuator, result, revert_ts, operator } ]
GET /api/stats aggregate counters (active incidents, FP24h, p95, …)
GET /api/policies [ { id, name, segments, mode, enabled } ]
GET /api/settings flat { key: stringified-value } map
GET /api/latency { p95_ms } (detect→mitigate, last 5 min window)

Mutating

Incidents (analyst+)

Method Path Body Notes
POST /api/incident-status `{ "id": 123, "status": "closed" \ "open" }` "closed" = acknowledge/quarantine; "open" = release

Bindings (responder+)

Method Path Body
POST /api/approve { "ip": "192.168.10.50", "mac": "aa:bb:cc:00:00:50" }
POST /api/reject { "ip": "192.168.10.50", "mac": "aa:bb:cc:00:00:50" }
POST /api/binding-add { "ip": "...", "mac": "...", "vlan": 10 }

Policies & settings (admin)

Method Path Body
POST /api/policy { "name": "...", "segments": "VLAN 10,VLAN 20", "mode": "GUARDED" }
POST /api/policy-toggle { "id": 1 }
POST /api/settings { "key": "autoMitigation", "value": "true" }

Users (admin)

Method Path Body
GET /api/users
POST /api/users { "username": "...", "password": "...", "role": "viewer" }
POST /api/user-role { "id": 7, "role": "responder" }
POST /api/user-delete { "id": 7 }

Live feed

GET /events                   Accept: text/event-stream

Server‑Sent Events stream of new detections as they hit PostgreSQL. Each event is a JSON object with the same shape as a /api/detections row. The dashboard uses this for incremental updates between the 5 s polling refresh.

Error model

  • 400 Bad Request — bad JSON, missing required field
  • 401 Unauthorized — missing/invalid bearer token (dashboard force‑logouts)
  • 403 Forbidden — role insufficient for the route (requiredRole() check)
  • 500 Internal Server Error — body is the error string from pgx/handler

Successful mutating endpoints return {"status":"ok"} unless otherwise specified.

Versioning

The API has no explicit version prefix yet. Until v2 ships, treat the contract as additive‑only: new fields may appear, existing ones don't change shape. Major changes will move to /api/v2/... and the v1 endpoints will run in parallel for ≥ one release cycle.