Skip to content

Users

Manage user accounts, authentication, and role assignments in Mantis.

FieldTypeDescription
idUUIDUnique identifier
emailStringEmail address (unique within the tenant)
usernameStringDisplay username (unique within the tenant)
password_hashStringArgon2id hash (local auth only)
display_nameStringOptional display name
statusEnumactive, inactive, locked, pending_verification, pending_approval
email_verifiedBooleanEmail verification status
tenant_idUUIDOptional tenant scope
last_login_atDateTimeLast successful login
created_atDateTimeAccount creation time
updated_atDateTimeLast update time (nullable)
StatusDescription
activeAccount is active and can log in
inactiveAccount is inactive (disabled by admin)
lockedAccount is locked (too many failed logins)
pending_verificationInvited; waiting on the user to activate
pending_approvalProvisioned by an identity provider, awaiting approval

Only active can sign in.

Terminal window
curl -X GET \
-H "Authorization: Bearer $TOKEN" \
"https://api.mantis.local/api/v1/admin/users?page=1&limit=20"

Response:

{
"data": [
{
"id": "019b937d-4862-8e07-ac3a-1b8589d1b807",
"email": "alice@example.com",
"username": "alice",
"display_name": "Alice Smith",
"status": "active",
"email_verified": true,
"role_count": 2,
"tenant_id": null,
"last_login_at": "2024-01-15T10:30:00Z",
"created_at": "2024-01-01T08:00:00Z"
}
],
"meta": {
"total": 50,
"page": 1,
"limit": 20,
"total_pages": 3,
"has_next": true,
"has_prev": false
}
}
Terminal window
curl -X GET \
-H "Authorization: Bearer $TOKEN" \
"https://api.mantis.local/api/v1/admin/users/$USER_ID"

Response includes roles, permissions, and external identities:

{
"id": "019b937d-4862-8e07-ac3a-1b8589d1b807",
"email": "alice@example.com",
"username": "alice",
"display_name": "Alice Smith",
"status": "active",
"email_verified": true,
"roles": [
{
"id": "019b937d-4862-8e07-ac3a-1b8589d1b807",
"name": "operator",
"description": "Can manage deployments and targets",
"is_system": true
}
],
"permissions": [
"deployments:create",
"deployments:read",
"targets:create",
"targets:read"
],
"tenant_id": null,
"tenant_name": null,
"external_identities": [
{
"provider_id": "019b937d-4862-8def-4567-001122334455",
"provider_name": "Okta",
"external_username": "alice.smith",
"external_email": "alice.smith@company.com",
"last_login_at": "2024-01-15T10:30:00Z"
}
],
"last_login_at": "2024-01-15T10:30:00Z",
"created_at": "2024-01-01T08:00:00Z",
"updated_at": "2024-01-10T14:00:00Z"
}

Creating a user sends an invitation. The account is created pending_verification with no usable password — the invitee sets their own through the emailed link, so an administrator never handles it. The request carries no password:

Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "bob@example.com",
"username": "bob",
"display_name": "Bob Jones",
"tenant_id": null,
"role_ids": ["019b937d-4862-84ae-9c2a-6ac230cc4c7e"]
}' \
"https://api.mantis.local/api/v1/admin/users"

The invitation needs a working platform mailer — see Platform email. Links expire after 7 days; POST /api/v1/admin/users/{id}/resend-invite issues a fresh one and invalidates the previous link.

Terminal window
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Alice S.",
"status": "active",
"email_verified": true
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID"
Terminal window
curl -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"https://api.mantis.local/api/v1/admin/users/$USER_ID"
Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"new_password": "NewSecurePassword456!"
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID/password"

An admin password reset is also the credential-containment action: it revokes the target user’s OAuth2 tokens, API keys and active sessions, not just the password. That is deliberate — an attacker holding a session or API key would otherwise keep access through a “lockout” reset — but it does mean resetting a password to help a user will break their running integrations.

An account with a TOTP enrolment answers POST /auth/login with 200 and requires_2fa: true — no access token. If the authenticator is gone, no password reset recovers it: the second factor is checked after the password, so the account stays unreachable and any non-interactive caller (a deploy, a script) fails on the missing token rather than on a clear authentication error.

Clear the factor from the host, which needs database access rather than a login:

Terminal window
sudo -u mantis mantisctl user disable-two-factor --email admin@mantis.local

It is idempotent — an account with no enrolment reports zero. The user can enrol a new authenticator afterwards.

Deploys never run this for you: your second factor is yours to keep. A deploy only authenticates when it has just recreated the database, and a freshly seeded admin has no enrolment — so an operator’s 2FA never blocks a converge.

Terminal window
curl -X GET \
-H "Authorization: Bearer $TOKEN" \
"https://api.mantis.local/api/v1/admin/users/$USER_ID/roles"
Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"role_ids": ["019b937d-4862-8e07-ac3a-1b8589d1b807", "019b937d-4862-84ae-9c2a-6ac230cc4c7e"]
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID/roles"
Terminal window
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"role_ids": ["019b937d-4862-8e07-ac3a-1b8589d1b807"]
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID/roles/remove"

An administrator invites the user; the user sets their own password:

Users can be provisioned automatically on first SSO login, provided the identity provider is scoped to a tenant and has auto-provisioning enabled (auto_create_users). System-wide (global) identity providers do not auto-provision new users (SEC-OIDC-02); link the provider to a tenant to enable this:

Terminal window
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "019b937d-4862-8387-9a86-df16bd76ca99"
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID"

Set tenant_id to null to make user global:

Terminal window
curl -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": null
}' \
"https://api.mantis.local/api/v1/admin/users/$USER_ID"

Password requirements are hardcoded and cannot be changed via configuration:

  • Minimum 12 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one digit
  • At least one special character (!@#$%^&*()_+-=[]{}|;:,.<>?)
  • Passwords hashed with Argon2id (library default parameters)
  • Original passwords never stored
  • Hash comparison for authentication

Users can have multiple external identities:

FieldDescription
provider_idIdentity provider reference
provider_nameName of the identity provider
external_usernameUsername from external provider
external_emailEmail from external provider
last_login_atLast SSO login via this provider

When a user logs in via SSO:

  1. System checks for an existing external_identity match.
  2. If found, updates last login and uses the linked user.
  3. If not found, creation/linking is gated, not automatic:
    • Auto-provisioning a new user requires the IdP to be tenant-scoped (a system-wide / NULL-tenant provider cannot provision).
    • Linking to an existing user by email additionally requires the provider’s trust_unverified_email = true and the ID token asserting a verified email; otherwise it fails closed with 403 Email is already associated with another account.

There is no unlink UI — this is a direct-database operation only:

DELETE FROM external_identities
WHERE user_id = $USER_ID
AND provider_id = $PROVIDER_ID;

All user operations are logged:

EventCategorySeverity
user.createduser_managementInfo
user.updateduser_managementInfo
user.deleteduser_managementInfo
user.password_resetuser_managementSecurity
user.roles_assignedauthorizationSecurity
user.roles_removedauthorizationSecurity

Example audit entry:

{
"event_type": "user.created",
"event_category": "user_management",
"actor_type": "user",
"actor_id": "019b937d-4862-8e07-ac3a-1b8589d1b801",
"actor_name": "admin",
"resource_type": "user",
"resource_id": "019b937d-4862-8e07-ac3a-1b8589d1b809",
"outcome": "success",
"outcome_reason": null,
"occurred_at": "2026-01-15T14:23:01Z"
}
  1. Use SSO when possible - Reduces password management overhead
  2. Assign minimum required roles - Follow least privilege principle
  3. Set tenant scope appropriately - Limit access to necessary resources
  4. Verify email addresses - Ensure contact information is accurate
  1. Set status to inactive - blocks new logins and refresh, but does not revoke anything already issued
  2. Reset the user’s password - this is what actually revokes their OAuth2 tokens, API keys and sessions
  3. Remove role assignments - clear all permissions
  4. Review owned resources - transfer ownership if needed
  5. Delete account - after transition period
  • Review active users quarterly
  • Verify role assignments match current responsibilities
  • Check for unused accounts (no recent login)
  • Audit external identity links
  1. Check account status:

    SELECT status FROM users WHERE email = 'user@example.com';
  2. Verify password (local auth):

    • Reset password via admin API
    • Check for caps lock / keyboard issues
  3. Check external identity (SSO):

    • Verify provider is configured
    • Check role mappings exist
    • Review IdP logs for errors
  1. List user’s current roles:

    Terminal window
    curl -s -H "Authorization: Bearer $TOKEN" \
    https://api.mantis.local/api/v1/admin/users/$USER_ID/roles
  2. Check role permissions:

    Terminal window
    curl -s -H "Authorization: Bearer $TOKEN" \
    https://api.mantis.local/api/v1/admin/roles/$ROLE_ID/permissions
  3. Assign missing role:

    Terminal window
    curl -X POST \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"role_ids": ["019b937d-4862-84ae-9c2a-6ac230cc4c7e"]}' \
    https://api.mantis.local/api/v1/admin/users/$USER_ID/roles