Users And Invitations
Create, update, delete, and invite tenant users from the platform admin perspective.
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
| Action | Route | Roles |
|---|---|---|
| List users | GET /api/v1/users | CLIENT_ADMIN, PLATFORM_ADMIN |
| Create user and invitation | POST /api/v1/users | CLIENT_ADMIN, PLATFORM_ADMIN |
| Update user profile | PUT /api/v1/users/{id} | CLIENT_ADMIN, PLATFORM_ADMIN |
| Delete user by body alias | DELETE /api/v1/users | CLIENT_ADMIN, PLATFORM_ADMIN |
| Delete user by path | DELETE /api/v1/users/{id} | CLIENT_ADMIN, PLATFORM_ADMIN |
| Accept invitation | POST /api/v1/invitations/accept | Public 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
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:
| Route | Notes |
|---|---|
DELETE /api/v1/users | Body 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
| Failure | Backend behavior |
|---|---|
| Duplicate tenant email and realm | CONFLICT. |
| Keycloak user provisioning failure | SERVICE_UNAVAILABLE, transaction rolled back. |
| Missing tenant or organization scope | BAD_REQUEST or FORBIDDEN depending on caller. |
| Invitation token missing, expired, consumed, or invalid | Structured BAD_REQUEST or FORBIDDEN from the invitation acceptance route. |
Admin Checklist
After creating a user:
| Check | Expected 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. |