Auth endpoints
All endpoints live under /api/v1/auth. Public endpoints accept no credentials. Protected endpoints require Authorization: Bearer <access_token>. Refresh tokens are delivered in an HttpOnly cookie for client_type=web and in the response body for client_type=mobile (D48).
POST /api/v1/auth/login
Exchange email and password for an access token and refresh token.
Since: v1.0 Required role: public
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
email | string | — | RFC 5321 mailbox |
password | string | — | 1-256 chars |
remember_me | bool | false | Extends refresh cookie lifetime from 7 to 30 days |
client_type | string | web | web or mobile |
Response 200
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2026-04-21T15:30:00Z", "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "display_name": "Jordan Park", "role": "operator", "org_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }}Error responses
| Code | Body.error | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing email or password |
| 401 | UNAUTHORIZED | Email not found or password mismatch (no separate invalid_credentials code in v1) |
| 403 | ACCOUNT_DISABLED | User record has enabled=false |
| 423 | ACCOUNT_LOCKED | 5 consecutive failed logins inside a 15-minute window |
| 429 | RATE_LIMITED | See /developer/rate-limits |
Examples
curl -X POST https://novavms.novalien.com/api/v1/auth/login \ -H "Content-Type: application/json" \const session = await novavms.auth.login({ password: 'correct-horse', clientType: 'mobile',});session = client.auth.login( password="correct-horse", client_type="mobile",)POST /api/v1/auth/refresh
Exchange a refresh token for a new access token. Web clients send the refresh token via cookie; mobile clients send it in the JSON body.
Since: v1.0 Required role: public (possession of refresh token)
Request body (mobile)
| Name | Type | Default | Constraints |
|---|---|---|---|
refresh_token | string | — | Opaque token from login |
client_type | string | web | web or mobile |
Response 200
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2026-04-21T16:30:00Z"}Error responses
| Code | Body.error.code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Refresh token revoked, expired, or user-agent mismatch (D-R2) |
| 429 | RATE_LIMITED | See /developer/rate-limits |
Examples
curl -X POST https://novavms.novalien.com/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token":"rt_2gH3k...","client_type":"mobile"}'const fresh = await novavms.auth.refresh({ refreshToken: 'rt_2gH3k...' });fresh = client.auth.refresh(refresh_token="rt_2gH3k...")POST /api/v1/auth/logout
Revoke the caller’s current session.
Since: v1.0 Required role: any authenticated user
Response 200
{"message": "logged out"}Error responses
| Code | Body.error.code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid token |
Examples
curl -X POST https://novavms.novalien.com/api/v1/auth/logout \ -H "Authorization: Bearer $ACCESS_TOKEN"await novavms.auth.logout();client.auth.logout()POST /api/v1/auth/forgot-password
Send a password-reset email. Returns 200 whether or not the address exists (anti-enumeration).
Since: v1.0 Required role: public
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
email | string | — | RFC 5321 mailbox |
Response 200
{"message": "if the address is registered, a reset email has been sent"}Error responses
| Code | Body.error.code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing email |
| 429 | RATE_LIMITED | See /developer/rate-limits |
Examples
curl -X POST https://novavms.novalien.com/api/v1/auth/forgot-password \ -H "Content-Type: application/json" \POST /api/v1/auth/reset-password
Complete a password reset using the token emailed by forgot-password.
Since: v1.0 Required role: public (possession of reset token)
Request body
| Name | Type | Default | Constraints |
|---|---|---|---|
token | string | — | One-time reset token |
new_password | string | — | 12-128 chars; upper, lower, digit, symbol |
Response 200
{"message": "password updated"}Error responses
| Code | Body.error.code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Password fails complexity rules |
| 401 | UNAUTHORIZED | Token expired, unknown, or reused |
Examples
curl -X POST https://novavms.novalien.com/api/v1/auth/reset-password \ -H "Content-Type: application/json" \ -d '{"token":"pr_9a8b7c...","new_password":"NewPassw0rd!2026"}'await novavms.auth.resetPassword({ token: 'pr_9a8b7c...', newPassword: 'NewPassw0rd!2026',});client.auth.reset_password(token="pr_9a8b7c...", new_password="NewPassw0rd!2026")GET /api/v1/auth/me
Return the caller’s profile, role, and org.
Since: v1.0 Required role: any authenticated user
Response 200
{ "id": "550e8400-e29b-41d4-a716-446655440000", "display_name": "Jordan Park", "role": "operator", "org_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "preferences": { "locale": "en-US", "timezone": "America/Los_Angeles" }}Error responses
| Code | Body.error.code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid token |
Examples
curl -H "Authorization: Bearer $ACCESS_TOKEN" \ https://novavms.novalien.com/api/v1/auth/meconst me = await novavms.auth.me();me = client.auth.me()Profile self-service sub-routes
The auth handler also exposes three live JWT-gated endpoints for profile self-service that are NOT yet broken out per-section above. See arch-api-spec.md §2 Authentication for the full request/response shapes:
PATCH /api/v1/auth/me— updatedisplay_nameand other self-editable profile fields. Handler:cloud/internal/accounts/handler.go:41.POST /api/v1/auth/change-password— change current-user password (requiresold_password+new_password). Handler:handler.go:42.PATCH /api/v1/auth/me/preferences— update preferences object (locale,timezone, etc.). Handler:handler.go:43.
All three require a valid Bearer access token; the org is taken from the JWT (composite-key isolation per D11). Standard error envelope per API error codes.