Authentication
Authentication
Haltless uses two kinds of credentials, and which one you use depends on what you're doing:
| Credential | Use it for | How you send it |
|---|---|---|
| Bearer access token | Reading and managing data as a signed-in user (machines, alerts, settings, reports) | Authorization: Bearer <access-token> |
| API key | Machine-to-machine access: streaming machine data in from the Edge Agent or your own scripts, or a service account for an integration | X-API-Key: <api-key> |
Both are sent over HTTPS to https://api.haltless.io/api/v1. Never put a credential in a URL query string or commit one to source control.
Bearer access tokens
Access tokens identify a signed-in person. You obtain one by logging in, send it on every request, and refresh it when it expires.
| Token | Lifetime |
|---|---|
| Access token | 30 minutes |
| Refresh cookie | 7 days, replaced on every refresh |
| MFA pending token (between password and code) | 5 minutes |
Log in to get a token
curl -X POST https://api.haltless.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "<your-password>" }'
{
"access_token": "<your-access-token>",
"token_type": "bearer",
"mfa_required": false,
"tenant_choice_required": false,
"tenants": [],
"password_expired": false
}
Alongside the response, Haltless sets a refresh token in a secure, httpOnly cookie. You never read that cookie yourself; it's used only to obtain new access tokens (see Refreshing).
One email in several organizations. The same email address can belong to more than one company. If the password matches in more than one, the response has "tenant_choice_required": true, an empty access_token, and the organizations in tenants (each with tenant_id and name). Send the same request again with the chosen tenant_id:
curl -X POST https://api.haltless.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "<your-password>", "tenant_id": "<chosen-tenant-id>" }'
Expired password. Passwords expire after 90 days. When password_expired is true, the token can still be used to change the password, but other requests return 403 until you do. See Errors.
Send the token
Include the access token in the Authorization header on every authenticated request:
curl https://api.haltless.io/api/v1/machines \
-H "Authorization: Bearer <your-access-token>"
Refreshing a token
When an access token expires, exchange your refresh cookie for a fresh access token instead of logging in again:
curl -X POST https://api.haltless.io/api/v1/auth/refresh \
--cookie "refresh_token=<refresh-cookie>"
Each refresh issues a new access token and rotates the refresh cookie; the previous one stops working. Build your client to refresh automatically when a request comes back 401 Unauthorized, then retry the request once. If two requests refresh at the same time, only the first succeeds, so share one refresh between them.
Logging out
curl -X POST https://api.haltless.io/api/v1/auth/logout \
-H "Authorization: Bearer <your-access-token>"
Logging out invalidates your current session so the tokens can no longer be used.
Multi-factor authentication
If MFA is enabled on an account, POST /auth/login responds with "mfa_required": true, and its access_token is a pending token that is only good for completing the sign-in. Complete it by verifying a code from the authenticator app, or a single-use recovery code:
curl -X POST https://api.haltless.io/api/v1/auth/mfa/verify \
-H "Content-Type: application/json" \
-d '{
"mfa_token": "<access_token-from-login>",
"code": "123456"
}'
A successful verification returns a normal access token and sets the refresh cookie, exactly like a password-only login.
Teams can sign in through their own identity provider (OIDC single sign-on). If SSO is set up for your organization, your login flow may redirect you there instead of asking for a password. Your account admin manages this.
Sign-in errors
| Status | Meaning |
|---|---|
401 | Wrong email or password |
403 | Correct password, but the email address has not been verified yet |
423 | Your company's account is suspended |
429 | Too many failed attempts: 10 failures within 10 minutes lock the account for 15 minutes |
API keys
API keys are built for machine-to-machine traffic. Unlike an access token, a key does not expire on a short clock (you can still give it an expiry date), which makes it a good fit for long-running agents and integrations. There are two kinds:
| Kind | What it can do |
|---|---|
| Edge agent (default) | What its scopes allow. The default scopes, ingest and agent, send readings and call the edge-agent heartbeat. read:machines and read:sensors add read-only access to machines and their readings |
| Service account | Acts as a member of your company with the role you give it, through the same endpoints a person in that role uses. It cannot manage users, roles, API keys, or sign-in settings |
Create a key
Create keys from the dashboard under Settings → API Keys, or from the API. Creating a key requires an account admin's access token:
curl -X POST https://api.haltless.io/api/v1/settings/api-keys \
-H "Authorization: Bearer <admin-access-token>" \
-H "Content-Type: application/json" \
-d '{ "name": "line-3-edge" }'
{
"id": "8c7b1e2a-0f3d-4a9c-9b21-1d6e5f0a7c34",
"name": "line-3-edge",
"key": "<your-new-api-key>",
"scopes": ["ingest", "agent"],
"tenant_id": "1d6e5f0a-7c34-4a9c-9b21-8c7b1e2a0f3d",
"created_at": "2026-08-07T10:00:00Z",
"kind": "edge_agent",
"role": null
}
The full key value is shown only once, at creation. Copy it immediately and store it in a secret manager. If you lose it, create a new key and revoke the old one.
For scopes, service accounts, and expiry dates, see Managing API keys.
Send a key
Pass the key in the X-API-Key header:
curl -X POST https://api.haltless.io/api/v1/ingest \
-H "X-API-Key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "readings": [ /* ... */ ] }'
Rotate a key
Give each agent or integration its own key so you can rotate one without disrupting the others. To rotate:
- Create a new key with
POST /api/v1/settings/api-keys. - Update the agent or integration to use the new key.
- Confirm data is still flowing, then revoke the old key with
DELETE /api/v1/settings/api-keys/{key_id}.
Use a distinct name per key so you can tell them apart later. For a full walkthrough of rotation and best practices, see Managing API keys.
Handling auth errors
| Status | Meaning | What to do |
|---|---|---|
401 Unauthorized | Missing, invalid, or expired credential | For an access token, refresh and retry; for an API key, check the header and that the key is still active |
402 Payment Required | Edge-agent key used while your company's account is suspended | Nothing to fix on your side; the Edge Agent pauses and resumes by itself once the account is reinstated |
403 Forbidden | Authenticated, but not permitted for this action | Use a credential with the right access, for example a key with the needed scope, or a Bearer token for management endpoints |
429 Too Many Requests | Rate limit exceeded | Back off and retry. See Rate limiting |
See Errors for the full error format.
Next steps
- See the API authentication reference for endpoint-level details.
- Learn key rotation and hygiene in Managing API keys.
- Put it all together in the Quickstart.