Skip to content

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.

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):

Terminal window
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 Client
novavms = 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/v1/auth/login) — available since v1.0. The client_type field is web or mobile (D48). Use mobile so NovaVMS returns the refresh token in the JSON body rather than as an HttpOnly cookie (cookies are only useful for browser clients):

Terminal window
curl -X POST https://novavms.novalien.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "CorrectHorseBattery!",
"client_type": "mobile",
"remember_me": false
}'
const res = await fetch('https://novavms.novalien.com/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
password: 'CorrectHorseBattery!',
client_type: 'mobile',
}),
});
const { access_token, refresh_token } = await res.json();
import httpx
r = httpx.post(
"https://novavms.novalien.com/api/v1/auth/login",
json={"email": "[email protected]", "password": "CorrectHorseBattery!", "client_type": "mobile"},
)
tokens = r.json() # {"access_token": "...", "refresh_token": "..."}

2. Call the API with the access token:

Terminal window
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
https://novavms.novalien.com/api/v1/cameras

3. Refresh before the access token expires (POST /api/v1/auth/refresh). Send the refresh token in the JSON body for client_type=mobile; for client_type=web the cookie travels automatically and you do not need to set the token:

Terminal window
curl -X POST https://novavms.novalien.com/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"'"$REFRESH_TOKEN"'","client_type":"mobile"}'

The server rotates the refresh token on every refresh — store the new one from the response (mobile) or trust the rotated cookie (web) and discard the old.

Token lifetimes

TokenDefault TTLWith remember_me: trueNotes
Access token (JWT)1 hour1 hourStateless; cannot be revoked mid-lifetime
Refresh token7 days30 daysOpaque; stored hashed server-side; rotated each refresh
Service-account API keyNo 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.