Camera endpoints
All endpoints live under /api/v1/cameras. Read endpoints are open to every authenticated role; viewers are site-scoped (D78). Write endpoints require Operator or above (D81). Manual triggers rate-limit to one call per camera per 5 seconds (D49).
GET /api/v1/cameras
List cameras in the caller’s org.
Since: v1.0 Required role: viewer (site-scoped)
Query parameters
| Name | Type | Default | Constraints |
|---|---|---|---|
site_id | uuid | — | Filters to the given site |
gateway_id | uuid | — | Filters to cameras bound to a gateway |
status | enum | — | online, offline, pending, error, disabled |
type | enum | — | rtsp, onvif |
connection_mode | enum | — | gateway, direct |
cursor | string | null | Pagination cursor |
limit | int | 20 | 1-100 |
Response 200
{ "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Loading Dock East", "site_id": "b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", "gateway_id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "connection_mode": "gateway", "type": "onvif", "status": "online", "active_tier": "main" } ], "next_cursor": null, "total": 1}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
| 403 | forbidden | Role insufficient |
| 429 | rate_limited | See /developer/rate-limits |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ "https://novavms.novalien.com/api/v1/cameras?site_id=b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b"const cameras = await novavms.cameras.list({ siteId: 'b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b',});cameras = client.cameras.list(site_id="b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b")POST /api/v1/cameras
Create a camera. Gateway-bound cameras validate ONVIF/RTSP credentials synchronously before returning; on failure the camera is rolled back and an error is returned. Direct-mode cameras register with go2rtc asynchronously.
Since: v1.0 Required role: operator
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
site_id | uuid | — | Required |
gateway_id | uuid | — | Required when connection_mode=gateway |
connection_mode | enum | gateway | gateway, direct |
name | string | — | 1-100 chars |
type | enum | — | rtsp, onvif |
rtsp_url | string | — | Required for type=rtsp |
rtsp_username | string | — | Optional |
rtsp_password | string | — | Optional |
host | string | — | Required for type=onvif |
port | int | 80 | ONVIF service port |
onvif_username | string | — | Required for type=onvif |
onvif_password | string | — | Required for type=onvif |
Response 201
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Loading Dock East", "site_id": "b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", "gateway_id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "connection_mode": "gateway", "type": "onvif", "status": "online", "created_at": "2026-04-21T14:05:00Z"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Missing field or gateway connection check failed |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Site or gateway not in caller’s org |
Examples
curl -X POST https://novavms.novalien.com/api/v1/cameras \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"site_id":"b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b","gateway_id":"8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c","connection_mode":"gateway","name":"Loading Dock East","type":"onvif","host":"10.0.0.63","port":2020,"onvif_username":"admin","onvif_password":"s3cret"}'const cam = await novavms.cameras.create({ siteId: 'b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b', gatewayId: '8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c', connectionMode: 'gateway', name: 'Loading Dock East', type: 'onvif', host: '10.0.0.63', port: 2020, onvifUsername: 'admin', onvifPassword: 's3cret',});cam = client.cameras.create( site_id="b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", gateway_id="8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", connection_mode="gateway", name="Loading Dock East", type="onvif", host="10.0.0.63", port=2020, onvif_username="admin", onvif_password="s3cret",)GET /api/v1/cameras/{id}
Return a single camera.
Since: v1.0 Required role: viewer (site-scoped)
Response 200
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Loading Dock East", "site_id": "b5e9f3a1-2c4d-4e6f-8a1b-3c5d7e9f1a2b", "gateway_id": "8f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c", "connection_mode": "gateway", "type": "onvif", "status": "online", "stream_transport": "tcp", "active_tier": "main", "created_at": "2026-04-21T14:05:00Z"}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 401 | auth_required | Missing or invalid token |
| 403 | forbidden | Viewer outside site scope |
| 404 | not_found | Camera not in caller’s org |
Examples
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/cameras/550e8400-e29b-41d4-a716-446655440000const cam = await novavms.cameras.get('550e8400-e29b-41d4-a716-446655440000');cam = client.cameras.get("550e8400-e29b-41d4-a716-446655440000")PATCH /api/v1/cameras/{id}
Update camera metadata or credentials.
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 |
rtsp_url | string | — | Triggers revalidation |
rtsp_username | string | — | Triggers revalidation |
rtsp_password | string | — | Triggers revalidation |
onvif_username | string | — | Triggers revalidation |
onvif_password | string | — | Triggers revalidation |
Response 200
Returns the updated camera object — same shape as GET /api/v1/cameras/{id}.
Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | validation_error | Credential change failed revalidation |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Camera not in caller’s org |
Examples
curl -X PATCH https://novavms.novalien.com/api/v1/cameras/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"name":"Loading Dock East (Rev 2)"}'await novavms.cameras.update('550e8400-e29b-41d4-a716-446655440000', { name: 'Loading Dock East (Rev 2)',});client.cameras.update( "550e8400-e29b-41d4-a716-446655440000", name="Loading Dock East (Rev 2)",)DELETE /api/v1/cameras/{id}
Soft-delete the camera. Events and recordings are preserved; the camera stops accepting streams.
Since: v1.0 Required role: operator
Response 204
Empty body.
Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 403 | forbidden | Role insufficient |
| 404 | not_found | Camera not in caller’s org |
Examples
curl -X DELETE https://novavms.novalien.com/api/v1/cameras/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer sk_live_abc123"await novavms.cameras.delete('550e8400-e29b-41d4-a716-446655440000');client.cameras.delete("550e8400-e29b-41d4-a716-446655440000")POST /api/v1/cameras/{id}/trigger
Manually record a clip (“Record Clip” button, D49). One trigger per camera per 5 seconds.
Since: v1.2 Required role: operator
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
duration_sec | int | 15 | 5-60 |
note | string | — | Up to 500 chars; stored on the event |
Response 202
{ "event_id": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "started_at": "2026-04-21T14:10:00Z", "expected_clip_duration_sec": 15}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | ai_unavailable | Camera is not online |
| 403 | forbidden | Role insufficient |
| 404 | not_found | Camera not in caller’s org |
| 429 | rate_limited | Retry-After header set to 5 seconds |
Examples
curl -X POST https://novavms.novalien.com/api/v1/cameras/550e8400-e29b-41d4-a716-446655440000/trigger \ -H "Authorization: Bearer sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{"duration_sec":30,"note":"Manager requested walkthrough"}'const ev = await novavms.cameras.triggerRecording( '550e8400-e29b-41d4-a716-446655440000', { durationSec: 30, note: 'Manager requested walkthrough' },);ev = client.cameras.trigger_recording( "550e8400-e29b-41d4-a716-446655440000", duration_sec=30, note="Manager requested walkthrough",)Sub-routes under /cameras/{id} (pointer)
The camera handler exposes additional live endpoints not yet broken out into per-section detail on this page. Each has a corresponding entry in docs/arch-api-spec.md §14 Cameras. All require a Bearer access token; site-access checks mirror GET /cameras/{id}.
GET /api/v1/cameras/{id}/diagnostics— debug bundle (Phase 6 device diagnostics). Handler:cloud/internal/devices/camera_handler.go:128.GET /api/v1/cameras/{id}/health— current online / offline / error tier. Handler:camera_handler.go:129.GET /api/v1/cameras/{id}/thumbnail— 200 PNG, 1-hour signed-storage URL. Handler:camera_handler.go:130.POST /api/v1/cameras/{id}/snapshot— request an on-demand snapshot from the gateway. Handler:camera_handler.go:144.POST /api/v1/cameras/{id}/unpair— remove the camera from its current gateway (does not soft-delete). Handler:camera_handler.go:138.POST /api/v1/cameras/validate-connection— pre-check ONVIF / RTSP credentials before creating the camera. Handler:camera_handler.go:140.POST /api/v1/cameras/{id}/ptz— pan / tilt / zoom continuous-move command (D20-RESTORE). Handler:cloud/internal/devices/ptz_handler.go:49.GET /api/v1/cameras/{id}/analysis-focus,PUT /api/v1/cameras/{id}/analysis-focus,GET /api/v1/cameras/{id}/analysis-focus/history— per-camera AI analysis-focus zones (Phase 6). Handler:cloud/internal/devices/analysis_focus_handler.go.GET /api/v1/cameras/{id}/nbd-override,PATCH /api/v1/cameras/{id}/nbd-override,DELETE /api/v1/cameras/{id}/nbd-override— per-camera NBD knob overrides (Phase 4.5, migration 000080). Handler:cloud/internal/devices/nbd_override_handler.go.GET /api/v1/cameras/{id}/recording-coverage,GET /api/v1/cameras/{id}/recordings,GET /api/v1/cameras/{id}/clip-at— playback / scrubbing sub-routes. Handler:cloud/internal/events/handler.go:1135-1137.GET /api/v1/analysis-focus-presets— list available analysis-focus presets (org-wide, not per-camera). Handler:analysis_focus_handler.go:36.
The OpenAPI yaml at /openapi.yaml does not yet inventory these routes (see Phase 8 GAP-8-16). The Phase 8-12 follow-ups track adding them.