Python SDK
The novavms-sdk package wraps the NovaVMS REST API (v1) with type-hinted Python clients. It supports CPython 3.10+ and ships both a synchronous NovaVMSClient and an asyncio AsyncNovaVMSClient with identical surface areas.
Install
pip install novavms-sdkSince v1.0. Requires httpx>=0.27 and pydantic>=2.5 (installed automatically).
Initialize
from novavms import NovaVMSClient
client = NovaVMSClient( api_key="sk_live_abc123def456", base_url="https://api.novavms.io",)For asyncio applications:
import asynciofrom novavms import AsyncNovaVMSClient
async def main() -> None: async with AsyncNovaVMSClient(api_key="sk_live_abc123def456") as client: cameras = await client.cameras.list() for camera in cameras.data: print(camera.name, camera.status)
asyncio.run(main())Client options
| Option | Type | Default | Since | Description |
|---|---|---|---|---|
api_key | str | — | v1.0 | Org-scoped API key. Required. |
base_url | str | https://api.novavms.io | v1.0 | Override for self-hosted deployments. |
timeout | float | 10.0 | v1.0 | Per-request timeout in seconds. |
max_retries | int | 3 | v1.0 | Retries on 429 and 5xx with exponential backoff. |
http_client | httpx.Client | None | auto | v1.0 | Inject a pre-configured httpx.Client (for proxies, transports). |
user_agent | str | novavms-sdk/<version> | v1.0 | Appended to the default User-Agent. |
Available methods
The client exposes one namespace per top-level resource:
| Namespace | Purpose |
|---|---|
client.cameras | List, get, create, update, delete cameras; fetch snapshots and live-view tokens. |
client.events | Query the event feed, fetch a single event, acknowledge, star, add notes. |
client.alerts | List alert history, acknowledge alerts, change status. |
client.rules | Create, read, update, delete alert rules; enable/disable. |
client.sites | Create, read, update, delete sites; manage site-scoped user access. |
client.users | Invite users, list members, change roles, revoke sessions. |
client.webhooks | Create, list, update, delete webhook definitions; test delivery. Secret rotation requires the Admin role. |
Each namespace exposes list, get, create, update, remove, plus resource-specific verbs (for example cameras.trigger(), events.acknowledge()).
cameras = client.cameras.list(site_id="8f2b3a71-0c4e-4b5f-9d1a-2e7c3a4b5d6f")event = client.events.get("3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f")client.rules.update("9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d", enabled=False)Error types
Errors use a standard Python exception hierarchy rooted at NovaVMSError.
| Class | Raised when | Since |
|---|---|---|
NovaVMSError | Any API error. Has status, code, request_id. | v1.0 |
AuthError | 401 or 403 — invalid key or missing scope. | v1.0 |
RateLimitError | 429 — exposes retry_after (seconds, float). | v1.0 |
ValidationError | 400 — field_errors: dict[str, list[str]]. | v1.0 |
NotFoundError | 404 — resource does not exist in this org. | v1.0 |
import timefrom novavms import NovaVMSClientfrom novavms.errors import AuthError, RateLimitError, NovaVMSError
client = NovaVMSClient(api_key="sk_live_abc123def456")
try: cameras = client.cameras.list()except RateLimitError as err: time.sleep(err.retry_after)except AuthError as err: raise RuntimeError("API key is invalid or revoked") from errexcept NovaVMSError as err: print(f"NovaVMS API error {err.request_id} {err.code}: {err.message}") raiseTyped responses
Every response is a Pydantic model generated from the OpenAPI spec shipped at /api/v1/openapi.yaml. An SDK version always matches an API version — no hand-written types drift.
from novavms.models import Camera, Event, AlertRule
def render(camera: Camera) -> str: return f"{camera.name} ({camera.status})"For the full list of types, see /developer/api/.
Pagination
List endpoints use opaque cursors. The SDK exposes a built-in iterator that handles the cursor walk for you:
for camera in client.cameras.iter(): process(camera)iter() returns a generator. It fetches the next page only when the current one is exhausted, and stops when the next cursor is None. Since v1.0.
For manual control:
cursor: str | None = Nonewhile True: page = client.events.list(cursor=cursor, limit=100) for event in page.data: process(event) cursor = page.next_cursor if cursor is None: breakThe async client exposes async for on aiter():
async for event in client.events.aiter(limit=100): await process(event)Related
- Make your first SDK call — end-to-end tutorial
- API keys — how to issue and scope keys (Admin role)
- Rate limits — per-key and per-org ceilings