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 field401 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.