Skip to content

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

NameTypeDefaultConstraints
emailstring—RFC 5321 mailbox
passwordstring—1-256 chars
remember_meboolfalseExtends refresh cookie lifetime from 7 to 30 days
client_typestringwebweb or mobile

Response 200

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-04-21T15:30:00Z",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]",
"display_name": "Jordan Park",
"role": "operator",
"org_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
}

Error responses

CodeBody.errorMeaning
400VALIDATION_ERRORMissing email or password
401UNAUTHORIZEDEmail not found or password mismatch (no separate invalid_credentials code in v1)
403ACCOUNT_DISABLEDUser record has enabled=false
423ACCOUNT_LOCKED5 consecutive failed logins inside a 15-minute window
429RATE_LIMITEDSee /developer/rate-limits

Examples

Terminal window
curl -X POST https://novavms.novalien.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"correct-horse","client_type":"mobile"}'
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)

NameTypeDefaultConstraints
refresh_tokenstring—Opaque token from login
client_typestringwebweb or mobile

Response 200

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-04-21T16:30:00Z"
}

Error responses

CodeBody.error.codeMeaning
401UNAUTHORIZEDRefresh token revoked, expired, or user-agent mismatch (D-R2)
429RATE_LIMITEDSee /developer/rate-limits

Examples

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

CodeBody.error.codeMeaning
401UNAUTHORIZEDMissing or invalid token

Examples

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

NameTypeDefaultConstraints
emailstring—RFC 5321 mailbox

Response 200

{"message": "if the address is registered, a reset email has been sent"}

Error responses

CodeBody.error.codeMeaning
400VALIDATION_ERRORMissing email
429RATE_LIMITEDSee /developer/rate-limits

Examples

Terminal window
curl -X POST https://novavms.novalien.com/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
await novavms.auth.forgotPassword({ email: '[email protected]' });
client.auth.forgot_password(email="[email protected]")

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

NameTypeDefaultConstraints
tokenstring—One-time reset token
new_passwordstring—12-128 chars; upper, lower, digit, symbol

Response 200

{"message": "password updated"}

Error responses

CodeBody.error.codeMeaning
400VALIDATION_ERRORPassword fails complexity rules
401UNAUTHORIZEDToken expired, unknown, or reused

Examples

Terminal window
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",
"email": "[email protected]",
"display_name": "Jordan Park",
"role": "operator",
"org_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"preferences": {
"locale": "en-US",
"timezone": "America/Los_Angeles"
}
}

Error responses

CodeBody.error.codeMeaning
401UNAUTHORIZEDMissing or invalid token

Examples

Terminal window
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
https://novavms.novalien.com/api/v1/auth/me
const 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 — update display_name and other self-editable profile fields. Handler: cloud/internal/accounts/handler.go:41.
  • POST /api/v1/auth/change-password — change current-user password (requires old_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.