Authentication
Most Haltless API endpoints authenticate with a bearer JWT access token. Telemetry ingestion authenticates with an API key instead — see Sensor Data and the API Keys guide.
Send the access token on every authenticated request:
Authorization: Bearer <jwt>
Access tokens are short-lived. A long-lived refresh token is returned as a secure, httpOnly cookie by `/api/v1/auth/login` and is used by `/api/v1/auth/refresh` to mint a new access token.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/v1/auth/login | Exchange email and password for an access token |
POST | /api/v1/auth/refresh | Get a fresh access token from the refresh cookie |
GET | /api/v1/auth/me | Return the authenticated user's profile |
POST | /api/v1/auth/logout | Revoke the current session |
POST | /api/v1/auth/mfa/verify | Complete an MFA challenge for a full token |
POST | /api/v1/auth/forgot-password | Email a password-reset link |
POST | /api/v1/auth/reset-password | Set a new password from a reset token |
GET | /api/v1/auth/sso/check | Check whether an email domain uses SSO |
POST | /api/v1/auth/sso/exchange | Exchange an SSO code for tokens |
Log in
POST /api/v1/auth/login
Request body
{
"email": "user@example.com",
"password": "<password>"
}
Response — 200 OK
{
"access_token": "<jwt>",
"token_type": "bearer",
"mfa_required": false,
"password_expired": false
}
When the account has multi-factor authentication enabled, the response instead returns a short-lived pending token with mfa_required set to true. Complete the challenge at `/api/v1/auth/mfa/verify` to receive a full access token.
Refresh an access token
POST /api/v1/auth/refresh
Sends the httpOnly refresh cookie set at login; no request body is required.
Response — 200 OK
{
"access_token": "<jwt>",
"token_type": "bearer"
}
Complete an MFA challenge
POST /api/v1/auth/mfa/verify
Request body
{
"mfa_token": "<pending-token>",
"code": "123456"
}
The code accepts a six-digit time-based one-time code or a single-use recovery code.
Response — 200 OK
{
"access_token": "<jwt>",
"token_type": "bearer"
}
Get the current user
GET /api/v1/auth/me
Requires a bearer JWT. Returns the authenticated user's profile.
Response — 200 OK
{
"id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"email": "user@example.com",
"full_name": "Jordan Rivera",
"role": "operator",
"mfa_enabled": true,
"timezone": "UTC",
"locale": "en",
"last_login_at": "2026-08-07T09:12:00Z"
}
Log out
POST /api/v1/auth/logout
Requires a bearer JWT. Revokes the current access and refresh tokens and clears the refresh cookie. Returns 204 No Content.
Password reset
POST /api/v1/auth/forgot-password emails a time-limited reset link to the address if it matches a user. It always returns 204 No Content, so it never reveals whether an email is registered.
{
"email": "user@example.com"
}
POST /api/v1/auth/reset-password consumes the emailed token and sets a new password:
{
"token": "<reset-token>",
"new_password": "<new-password>"
}
Returns 204 No Content on success.
Single sign-on
If your organization uses SSO, check a login email's domain before showing a password field:
GET /api/v1/auth/sso/check?email=user@example.com
Response — 200 OK
{
"has_sso": true,
"provider_name": "Example IdP",
"redirect_url": "https://idp.example.com/authorize?..."
}
Redirect the user to redirect_url. After the identity provider authenticates the user, your login page receives a short-lived code that you exchange for tokens:
POST /api/v1/auth/sso/exchange
{
"code": "<sso-code>"
}
Response — 200 OK
{
"access_token": "<jwt>",
"token_type": "bearer"
}
See the Authentication getting-started guide for a full walkthrough.