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

  1. Check existing users: GET /v2/{organizationId}/Users?filter=emails.value eq "..." or filter by externalId.
  2. If the user exists, PATCH them instead of creating a new one.
  3. Configure the IdP to treat 409 as 'already exists' and skip or update.
  4. 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

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


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/ff51acf441b6b779. Report an issue: GitHub.