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/auth/login) — available since v1.0:

Terminal window
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 httpx
r = 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:

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

3. Refresh before the access token expires (POST /api/auth/refresh):

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

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 expiryRevoke 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.