bitwarden/server · error · ConflictException
User already exists.
Error message
User already exists.
What it means
Thrown as a ConflictException (HTTP 409) by PostUserCommand.InviteScimOrganizationUserAsync_vNext when an existing organization user already has the same email (case-insensitive) or the same externalId (case-insensitive). This is the pre-invite deduplication check in the vNext invitation path.
Source
Thrown at bitwarden_license/src/Scim/Users/PostUserCommand.cs:119
if (organization is null)
{
throw new NotFoundException();
}
var request = model.ToRequest(
scimProvider: scimProvider,
organization: organization,
performedAt: timeProvider.GetUtcNow());
var orgUsers = await organizationUserRepository
.GetManyDetailsByOrganizationAsync(request.Organization.Id);
if (orgUsers.Any(existingUser =>
request.Invites.First().Email.Equals(existingUser.Email, StringComparison.OrdinalIgnoreCase) ||
request.Invites.First().ExternalId.Equals(existingUser.ExternalId, StringComparison.OrdinalIgnoreCase)))
{
throw new ConflictException("User already exists.");
}
var result = await inviteOrganizationUsersCommand.InviteScimOrganizationUserAsync(request);
var invitedOrganizationUserId = result switch
{
Success<ScimInviteOrganizationUsersResponse> success => success.Value.InvitedUser.Id,
Failure<ScimInviteOrganizationUsersResponse> { Error.Message: NoUsersToInviteError.Code } => (Guid?)null,
Failure<ScimInviteOrganizationUsersResponse> failure => throw MapToBitException(failure.Error),
_ => throw new InvalidOperationException()
};
var organizationUser = invitedOrganizationUserId.HasValue
? await organizationUserRepository.GetDetailsByIdAsync(invitedOrganizationUserId.Value)
: null;
return organizationUser;
}View on GitHub (pinned to e93b962371)
Solutions
- Check existing users: GET /v2/{organizationId}/Users?filter=emails.value eq "..." or filter by externalId.
- If the user exists, PATCH them instead of creating a new one.
- Configure the IdP to treat 409 as 'already exists' and skip or update.
- Deduplicate the source directory for both email and externalId.
Defensive patterns
Strategy: validation
Validate before calling
// Before creating, check if user exists by email or externalId
var existing = await scimClient.ListUsersAsync(orgId, filter: $"emails.value eq \"{email}\"");
if (!existing.Any()) existing = await scimClient.ListUsersAsync(orgId, filter: $"externalId eq \"{externalId}\"");
if (existing.Any()) { /* PATCH instead of POST */ } Try / catch
try { await scimClient.CreateUserAsync(orgId, model); }
catch (ScimException ex) when (ex.StatusCode == 409)
{ // user likely exists — fetch and PATCH instead
var existing = await scimClient.ListUsersAsync(orgId, filter: $"emails.value eq \"{email}\"");
if (existing.Any()) await scimClient.PatchUserAsync(orgId, existing.First().Id, patchModel); } Prevention
- Implement idempotent user creation: on 409, fall back to lookup-and-PATCH.
- Deduplicate emails and externalIds in the source directory.
- Configure the IdP to treat 409 as 'already exists' and skip or update.
When it happens
Trigger: POST /v2/{organizationId}/Users where the email or externalId in the invite matches an existing user in the org. Common when the IdP re-sends a user creation that already succeeded or when two directory entries resolve to the same email/externalId.
Common situations: IdP retries a create after a timeout where the first succeeded. User was invited manually and the IdP now also tries to provision them. Email aliases or case variations cause false matches. Directory has duplicate entries.
Related errors
- Conflict.
- ExternalId already exists for another user.
- ExternalId already exists for another group.
- ExternalId cannot exceed 300 characters.
- result.AsError.Message
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/ff51acf441b6b779.
Report an issue: GitHub.