Skip to content

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

CodeBody.errorMeaning
401auth_requiredMissing or invalid token
403forbiddenRole insufficient

Examples

Terminal window
curl -H "Authorization: Bearer sk_live_abc123" \
https://novavms.novalien.com/api/v1/gateways
const 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

NameTypeDefaultConstraints
namestring1-100 chars
site_iduuidMust 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

CodeBody.errorMeaning
400validation_errorMissing name or site_id
403forbiddenRole insufficient
429rate_limitedMore than 5 active pairing codes

Examples

Terminal window
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

NameTypeDefaultConstraints
pairing_codestring6 chars
namestringGateway’s self-reported name; used only if the registered gateway has none
versionstringGateway build version
local_ipstringGateway’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

CodeBody.errorMeaning
400validation_errorMissing pairing_code
404not_foundUnknown pairing code
409conflictCode already redeemed
410goneCode expired (older than 10 minutes)

Examples

Terminal window
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

CodeBody.errorMeaning
401auth_requiredMissing or invalid token
404not_foundGateway not in caller’s org

Examples

Terminal window
curl -H "Authorization: Bearer sk_live_abc123" \
https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
const 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

CodeBody.errorMeaning
404not_foundGateway not in caller’s org

Examples

Terminal window
curl -H "Authorization: Bearer sk_live_abc123" \
https://novavms.novalien.com/api/v1/gateways/8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/telemetry
const 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

NameTypeDefaultConstraints
namestring1-100 chars
site_iduuidMust exist in caller’s org
statusenumdisabled or offline only

Response 200

Returns the updated gateway — same shape as GET /api/v1/gateways/{id}.

Error responses

CodeBody.errorMeaning
400validation_errorNo valid fields, invalid status, or invalid transition
403forbiddenRole insufficient
404not_foundGateway not in caller’s org

Examples

Terminal window
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

CodeBody.errorMeaning
400validation_errorGateway still has bound cameras
403forbiddenRole insufficient
404not_foundGateway not in caller’s org

Examples

Terminal window
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")