Users And Invitations

Platform admins can create and maintain tenant users through the tenant API when the target tenant and organization are supplied. Client admins can perform the same workflow inside their own organization scope.

Routes

ActionRouteRoles
List usersGET /api/v1/usersCLIENT_ADMIN, PLATFORM_ADMIN
Create user and invitationPOST /api/v1/usersCLIENT_ADMIN, PLATFORM_ADMIN
Update user profilePUT /api/v1/users/{id}CLIENT_ADMIN, PLATFORM_ADMIN
Delete user by body aliasDELETE /api/v1/usersCLIENT_ADMIN, PLATFORM_ADMIN
Delete user by pathDELETE /api/v1/users/{id}CLIENT_ADMIN, PLATFORM_ADMIN
Accept invitationPOST /api/v1/invitations/acceptPublic token route

Create User

Example request:

  {
  "tenant_id": "11111111-1111-4111-8111-111111111111",
  "organization_id": "22222222-2222-4222-8222-222222222222",
  "email": "client-tech@example.test",
  "role": "CLIENT_TECH",
  "expires_at": "2026-12-31T23:59:59Z"
}
  

For organization users, the backend creates an invitation notification. If the Keycloak Admin provisioner is configured, the backend provisions the Keycloak user with the profile attributes needed by the portal and sends the user through the password update flow.

Invitation links are absolute URLs built from PUBLIC_INVITATION_BASE_URL. Configure it before creating users:

  PUBLIC_INVITATION_BASE_URL=https://wipe.example
  

The resulting link has this form:

  https://wipe.example/api/v1/invitations/accept?invitation_id=<id>&token=<token>
  

If PUBLIC_INVITATION_BASE_URL is empty, the backend falls back to PUBLIC_API_BASE_URL, then to the origin of PUBLIC_VERIFY_BASE_URL.

Profile Updates

PUT /api/v1/users/{id} updates tenant-scoped fields. The backend only syncs Keycloak when synced fields change, such as email, organization, role, realm, or expiration. A manual keycloak_user_id update alone does not trigger profile sync.

Delete Users

Both delete routes are protected writes:

RouteNotes
DELETE /api/v1/usersBody contains { "id": "<user_id>" }.
DELETE /api/v1/users/{id}User ID comes from the path.

When MFA recency is enabled, both routes require recent MFA.

Failure Semantics

FailureBackend behavior
Duplicate tenant email and realmCONFLICT.
Keycloak user provisioning failureSERVICE_UNAVAILABLE, transaction rolled back.
Missing tenant or organization scopeBAD_REQUEST or FORBIDDEN depending on caller.
Invitation token missing, expired, consumed, or invalidStructured BAD_REQUEST or FORBIDDEN from the invitation acceptance route.

Admin Checklist

After creating a user:

CheckExpected result
User exists in GET /api/v1/users.Status and role are correct.
Invitation email appears in Mailpit or SMTP provider logs.Link uses the public host, not localhost unless running local dev.
Email template renders HTML, not raw JSON.Logo and app name come from organization branding when configured.
Keycloak user exists when provisioner is enabled.Required action includes password update for invitations.