Skip to content

Auth flow

Step 1 — Login

curl -X POST https://your-domain.tld/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"..."}'

Response (without 2FA)

{
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "token_type": "bearer",
  "expires_in": 86400
}

Response (with 2FA)

{
  "two_fa_required": true,
  "challenge_token": "tx_..."
}

The user has 2FA enabled (authenticator app or passkey). Continue with the challenge_token and the TOTP code:

Step 2 — Verify 2FA

curl -X POST https://your-domain.tld/api/v1/auth/2fa/verify \
  -H "Content-Type: application/json" \
  -d '{"challenge_token":"tx_...","code":"123456"}'

Response

{
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_in": 86400
}

Wrong code: 401 + attempts_remaining in body. After 5 failures: 429 + Retry-After: 1800.

Step 3 — Requests with JWT

curl https://your-domain.tld/api/v1/hosts \
  -H "Authorization: Bearer eyJhbGc..."

Refresh

curl -X POST https://your-domain.tld/api/v1/auth/refresh \
  -H "Authorization: Bearer <refresh_token>"

Returns a new access_token (and new refresh_token). Refresh token is single-use.

Logout

curl -X POST https://your-domain.tld/api/v1/auth/logout \
  -H "Authorization: Bearer <access_token>"

Server invalidates the refresh token, frontend discards the access token.

Collector API keys are not user auth

Collectors authenticate with their own API key (X-API-Key header, stored as a SHA256 hash), agents with their own agent token (X-Agent-Token header). Both are accepted exclusively by the receiver for check-result uploads — they are not a substitute for the JWT login and don't work against any /api/v1/ user endpoint (hosts, profiles, dashboards, etc.). For scripts/CI that need to talk to the user API, the regular login flow above (email/password → JWT) is the only path — there is currently no separate API-key concept for users.

A collector key is created when you add a collector (/collectors in the frontend) and shown once in plaintext there; an agent token is created when you set up an agent on a host. Both belong to exactly one resource (collector or host), not to a user.

Permission failures

Missing permission:

{
  "detail": {
    "error": "permission_denied",
    "missing": "host.delete"
  }
}

HTTP 403.

Tenant scope

JWT contains tenant_id (own tenant) and tenant_scope (null for super admin, otherwise tenant UUID).

Cross-tenant queries (super admin) optional via query: ?tenant_id=<uuid>. Without query you get your own tenant.

Rate limits

  • Login/2FA: 10 req/min per IP
  • Other endpoints: 600 req/min per user

On exceed: 429 + Retry-After.

Time sync

JWT exp claim assumes client and server are time-synced (default 30 s tolerance). With server clock wrong (no NTP), phantom 401 errors appear.

Next