Gateway endpoints
Gateway endpoints
Gateways live under /api/v1/gateways. Gateway pairing is a two-step flow: an operator generates a pairing code (POST /generate-pairing-code), the gateway redeems it via the public endpoint (POST /pair). Gateways do not heartbeat over REST — once paired they connect via WebSocket at /api/v1/gateways/{id}/ws and push telemetry on that channel. The read-side telemetry snapshot is exposed at GET /api/v1/gateways/{id}/telemetry.
GET /api/v1/gateways
List gateways in the caller’s org.
Since: v1.0 Required role: viewer (site-scoped)
Response 200
{ "data": [ { "id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "name": "Acme Logistics — Warehouse A", "site_id": "b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", "status": "online", "version": "2026.04.12", "local_ip": "192.168.10.24", "last_heartbeat_at": "2026-04-21T14:20:04Z" } ], "total": 1}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
| 403 | forbidden | Role insufficient |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/gatewaysconst gateways = await novavms.gateways.list();gateways = client.gateways.list()POST /api/v1/gateways/generate-pairing-code
Generate a 6-character pairing code. The returned code is redeemable via POST /api/v1/gateways/pair for 10 minutes. Maximum 5 active (unredeemed) codes per org.
Since: v1.0 Required role: operator
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
name | string | — | 1-100 chars |
site_id | uuid | — | Must exist in caller’s org |
Response 201
{ "id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "pairing_code": "A7B3CX", "expires_at": "2026-04-21T14:30:00Z", "status": "registered"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Missing name or site_id |
| 403 | forbidden | Role insufficient |
| 429 | rate_limited | More than 5 active pairing codes |
Examples
curl -X POST https://novavms.novalien.com/api/v1/gateways/generate-pairing-code \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"name":"Acme Logistics — Warehouse A","site_id":"b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b"}'const pairing = await novavms.gateways.generatePairingCode({ name: 'Acme Logistics — Warehouse A', siteId: 'b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b',});pairing = client.gateways.generate_pairing_code( name="Acme Logistics — Warehouse A", site_id="b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b",)POST /api/v1/gateways/pair
Redeem a pairing code. This is a public endpoint — the gateway itself calls it on first boot to claim an API key. It must not be called by other clients.
Since: v1.0 Required role: public (possession of pairing code)
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
pairing_code | string | — | 6 chars |
name | string | — | Gateway’s self-reported name; used only if the registered gateway has none |
version | string | — | Gateway build version |
local_ip | string | — | Gateway’s LAN IP |
Response 200
{ "gateway_id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "api_key": "gw_ABCDEF0123456789...", "org_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "ice_servers": [ { "urls": ["turn:turn.novavms.novalien.com:3478"], "username": "novavms", "credential": "ephemeral-credential" } ]}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Missing pairing_code |
| 404 | not_found | Unknown pairing code |
| 409 | conflict | Code already redeemed |
| 410 | gone | Code expired (older than 10 minutes) |
Examples
curl -X POST https://novavms.novalien.com/api/v1/gateways/pair \ -H "Content-Type: application/json" \ -d '{"pairing_code":"A7B3CX","name":"Acme Logistics — Warehouse A","version":"2026.04.12","local_ip":"192.168.10.24"}'const paired = await novavms.gateways.pair({ pairingCode: 'A7B3CX', name: 'Acme Logistics — Warehouse A', version: '2026.04.12', localIp: '192.168.10.24',});paired = client.gateways.pair( pairing_code="A7B3CX", name="Acme Logistics — Warehouse A", version="2026.04.12", local_ip="192.168.10.24",)GET /api/v1/gateways/{id}
Return a single gateway.
Since: v1.0 Required role: viewer (site-scoped)
Response 200
{ "id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "name": "Acme Logistics — Warehouse A", "site_id": "b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", "status": "online", "version": "2026.04.12", "local_ip": "192.168.10.24", "last_heartbeat_at": "2026-04-21T14:20:04Z", "created_at": "2026-02-01T09:00:00Z"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
| 404 | not_found | Gateway not in caller’s org |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5cconst gw = await novavms.gateways.get('8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c');gw = client.gateways.get("8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c")GET /api/v1/gateways/{id}/telemetry
Return the latest telemetry reported by the gateway over its WebSocket (CPU, memory, last heartbeat, recording status, network throughput). There is no REST heartbeat endpoint — the gateway pushes heartbeats on its WebSocket channel at /api/v1/gateways/{id}/ws; use this telemetry snapshot to observe them.
Since: v1.1 Required role: viewer (site-scoped)
Response 200
{ "gateway_id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "cpu_percent": 18.4, "memory_mb": 742, "last_heartbeat_at": "2026-04-21T14:20:04Z", "camera_recording_status": { "550e8400-e29b-41d4-a716-446655440000": "recording" }, "network_throughput": { "upstream_mbps": 5.2, "downstream_mbps": 28.6 }}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 404 | not_found | Gateway not in caller’s org |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/telemetryconst telemetry = await novavms.gateways.telemetry( '8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c',);telemetry = client.gateways.telemetry("8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c")PATCH /api/v1/gateways/{id}
Update gateway metadata. Only name, site_id, and status are accepted. status may be set to disabled or offline only; valid transitions are online|offline|registered → disabled and disabled → offline.
Since: v1.0 Required role: operator
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
name | string | — | 1-100 chars |
site_id | uuid | — | Must exist in caller’s org |
status | enum | — | disabled or offline only |
Response 200
Returns the updated gateway — same shape as GET /api/v1/gateways/{id}.
Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | No valid fields, invalid status, or invalid transition |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Gateway not in caller’s org |
Examples
curl -X PATCH https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"name":"Acme Logistics — Warehouse A (Rev 2)"}'await novavms.gateways.update('8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c', { name: 'Acme Logistics — Warehouse A (Rev 2)',});client.gateways.update( "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", name="Acme Logistics — Warehouse A (Rev 2)",)DELETE /api/v1/gateways/{id}
Unpair the gateway and remove it from the org. All cameras bound to this gateway must be moved or deleted first.
Since: v1.0 Required role: operator
Response 204
Empty body.
Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Gateway still has bound cameras |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Gateway not in caller’s org |
Examples
curl -X DELETE https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \ -H "Authorization: Bearer sk_live_abc123"await novavms.gateways.delete('8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c');client.gateways.delete("8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c")