{"record":{"id":"c1d1d99aeca7c335","repo":"bitwarden/server","slug":"invite-the-user-first","errorCode":null,"errorMessage":"Invite the user first.","messagePattern":"Invite the user first\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs","lineNumber":358,"sourceCode":"                await _mailService.SendProviderConfirmedEmailAsync(provider.DisplayName(), user.Email);\n                result.Add(Tuple.Create(providerUser, \"\"));\n            }\n            catch (BadRequestException e)\n            {\n                result.Add(Tuple.Create(providerUser, e.Message));\n            }\n        }\n\n        await _eventService.LogProviderUsersEventAsync(events);\n\n        return result;\n    }\n\n    public async Task SaveUserAsync(ProviderUser user, Guid savingUserId)\n    {\n        if (user.Id.Equals(default))\n        {\n            throw new BadRequestException(\"Invite the user first.\");\n        }\n\n        if (user.Type != ProviderUserType.ProviderAdmin &&\n            !await HasConfirmedProviderAdminExceptAsync(user.ProviderId, new[] { user.Id }))\n        {\n            throw new BadRequestException(\"Provider must have at least one confirmed ProviderAdmin.\");\n        }\n\n        await _providerUserRepository.ReplaceAsync(user);\n        await _eventService.LogProviderUserEventAsync(user, EventType.ProviderUser_Updated);\n    }\n\n    public async Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId,\n        IEnumerable<Guid> providerUserIds, Guid deletingUserId)\n    {\n        var provider = await _providerRepository.GetByIdAsync(providerId);\n\n        if (provider == null)","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs#L340-L376","documentation":"Thrown inside SaveUserAsync when the ProviderUser's Id equals default (Guid.Empty), meaning the entity was never persisted/invited. SaveUserAsync updates an existing ProviderUser; it does not create one. BadRequestException (HTTP 400).","triggerScenarios":"Calling SaveUserAsync with a freshly constructed ProviderUser that was never created via InviteUserAsync.","commonSituations":"Constructing a ProviderUser in code and calling SaveUserAsync instead of InviteUserAsync; detached entity not yet persisted.","solutions":["Use InviteUserAsync to create the ProviderUser first, then SaveUserAsync for updates.","Ensure ProviderUser.Id is a non-default Guid before calling SaveUserAsync.","Check user.Id != Guid.Empty as a precondition."],"exampleFix":"// before\nawait _providerService.SaveUserAsync(newProviderUser, savingUserId);\n\n// after\nif (newProviderUser.Id == Guid.Empty)\n    await _providerService.InviteUserAsync(invite); // create first\nelse\n    await _providerService.SaveUserAsync(newProviderUser, savingUserId);","handlingStrategy":"validation","validationCode":"if (user.Id == Guid.Empty)\n    throw new InvalidOperationException(\"ProviderUser has not been invited/persisted yet. Call InviteUserAsync first.\");","typeGuard":"static bool IsPersisted(ProviderUser user) => user.Id != Guid.Empty;","tryCatchPattern":"try { await _providerService.SaveUserAsync(user, savingUserId); }\ncatch (BadRequestException ex) when (ex.Message == \"Invite the user first.\")\n{ /* route to InviteUserAsync instead */ }","preventionTips":["Use InviteUserAsync to create; SaveUserAsync only to update.","Assert user.Id != Guid.Empty before saving.","Do not construct fresh ProviderUser entities for SaveUserAsync."],"tags":["provider","save","persistence","validation","bitwarden"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}