Authenticate to the API
NovaVMS accepts two credential types on /api/v1/*: a service-account API key (for server-to-server integrations) and a user JWT access token (for customer-facing apps acting on behalf of a human). Both travel in the Authorization: Bearer ... header. Pick one per integration — do not mix.
Service-account API keys (recommended for integrations)
A service-account key represents a non-human caller with a fixed role and scope. It does not expire on a timer and it does not carry a session. Use this for ticketing bridges, SIEM collectors, backup exporters, and anything else where there is no logged-in user.
Get a key. An admin or owner mints the key from the admin UI at /admin/service-accounts. See Rotate an API key for the exact UI flow. The key is shown once; copy it straight into your secret store.
Send the key. Put it in the Authorization header on every request (since v1.0):
curl -H "Authorization: Bearer sk_live_abc123" \ https://novavms.novalien.com/api/v1/cameras// TypeScript (@novavms/sdk >= 1.0.0)import { createClient } from '@novavms/sdk';const novavms = createClient({ apiKey: 'sk_live_abc123' });# Python (novavms >= 1.0.0)from novavms import Clientnovavms = Client(api_key="sk_live_abc123")Key prefixes. sk_live_ is a production key. sk_test_ is a sandbox-org key and will return 404 on production-only resources. See API keys for the full prefix and quota rules.
User JWT (for a customer app calling on behalf of a user)
Use this when a human signs in through your product and you call NovaVMS with their identity. The flow is email + password exchanged for a short-lived access token plus a longer-lived refresh token.
1. Login (POST /api/auth/login) — available since v1.0:
curl -X POST https://novavms.novalien.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "ops@example.com", "password": "CorrectHorseBattery!", "client_type": "api", "remember_me": false }'const res = await fetch('https://novavms.novalien.com/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'ops@example.com', password: 'CorrectHorseBattery!', client_type: 'api', }),});const { access_token, refresh_token } = await res.json();import httpxr = httpx.post( "https://novavms.novalien.com/api/auth/login", json={"email": "ops@example.com", "password": "CorrectHorseBattery!", "client_type": "api"},)tokens = r.json() # {"access_token": "...", "refresh_token": "..."}Set client_type: "api" so NovaVMS returns the refresh token in the response body instead of as an HttpOnly cookie (cookies are only useful for browser clients).
2. Call the API with the access token:
curl -H "Authorization: Bearer $ACCESS_TOKEN" \ https://novavms.novalien.com/api/v1/cameras3. Refresh before the access token expires (POST /api/auth/refresh):
curl -X POST https://novavms.novalien.com/api/auth/refresh \ -H "Authorization: Bearer $REFRESH_TOKEN"The server rotates the refresh token on every refresh — store the new one and discard the old.
Token lifetimes
| Token | Default TTL | With remember_me: true | Notes |
|---|---|---|---|
| Access token (JWT) | 1 hour | 1 hour | Stateless; cannot be revoked mid-lifetime |
| Refresh token | 7 days | 30 days | Opaque; stored hashed server-side; rotated each refresh |
| Service-account API key | No expiry | — | Revoke via admin UI or set expires_at explicitly |
User-Agent binding. Refresh tokens are bound to the User-Agent string captured at login. A refresh attempt with a different User-Agent returns 401 SESSION_MISMATCH and the user must re-login. No IP binding — that would break mobile clients, VPN users, and anyone behind a CGNAT. This is decision D-R2.
Related
- Roles and permissions — what a token can do once authenticated
- Rotate an API key — admin-side flow for issuing and revoking keys
- Error codes — meaning of
401 UNAUTHORIZED,401 SESSION_MISMATCH,423 ACCOUNT_LOCKED