Skip to content

API overview

The appliance exposes a REST API under /api/v1 and a WebSocket stream at /ws/v1/stream, both on port 5080. The operator console uses the same interfaces.

The full REST contract, with every request and response body, is the OpenAPI file /openapi/rest-v1.json. It is generated from the code and checked against real responses.

SurfaceUse
/api/v1/*REST routes, authenticated per route
/ws/v1/streamLive detections, camera status and alarm events as JSON
/whep/*WebRTC video signalling. The video itself travels from the relay to the browser, not through the API
GET /health/liveAnonymous liveness check. It answers 200 when the service is up
TopicRule
AuthenticationSend Authorization: Bearer <access token>. An access token is valid for 15 minutes. The refresh token is an HttpOnly cookie, ev_refresh, valid for 14 days.
Rolesread (Administrator, Engineer, Operator), configure-inference (Administrator, Engineer) and administer (Administrator).
Unclaimed deviceUntil an administrator is claimed, every path answers 403 NOT_PROVISIONED except /health/live, /api/v1/setup/* and /api/v1/system/info.
Errorsapplication/problem+json with a stable code. Branch on code, never on title or detail, which are prose and can be reworded. Field validation is 422 VALIDATION_FAILED with an errors map keyed by the JSON field name.
Concurrent editsReads of editable rows carry a weak ETag. Send it back as If-Match on a write. A missing If-Match gets 428, and a stale one gets 412 with the current version in current.
Partial updatesIn a PATCH, a missing field means leave it unchanged, and an explicit null means clear it.
ListsAlways an object with an items array. Offset paging returns total, page and pageSize. Keyset paging returns total and nextCursor.
Asynchronous work202 Accepted means the work was requested, not finished. Read the outcome from a status route.
AuditEvery change that reaches its handler writes an audit entry named resource.action, for example camera.update.
  1. Request a ticket with POST /api/v1/ws-tickets and a bearer token. The reply is { ticket, expiresInSeconds }. A ticket works once and lasts 30 seconds.
  2. Open GET /ws/v1/stream?ticket=<ticket> as a WebSocket. A missing, expired or used ticket answers 401 before the handshake.
  3. The first frame is hello. The socket starts with no cameras, so send { "type": "subscribe", "cameras": ["<camera id>"] } for up to 64 cameras.

A detection arrives as a JSON object with no type field. Every other frame has a string type: hello, subscribed, clock, cameraStatus, event and alertState. Clients tell the two apart by whether type is present.

Up to 16 viewers can be connected at once by default. When the limit is reached the server closes the socket with 1008 too many clients. A client that does not read fast enough is closed with 1008 too slow, and should reconnect.

The version is in the path, /api/v1 and /ws/v1. Within a version, new routes, new optional request fields, new response fields and new error codes can be added. Ignore fields you do not know, and handle an unknown error code by its HTTP status class. Removing or renaming a route or field, changing a type or meaning, or making a policy stricter is a breaking change. It goes through a deprecation period or a new version path.

The REST API, the live stream and the detection payload are versioned separately. The detection payload carries schemaVersion, currently "1.0".