bitwarden/server · error · BadRequestException
AccountKeys are only supported for V2 encryption.
Error message
AccountKeys are only supported for V2 encryption.
What it means
In POST /accounts/keys, when model.AccountKeys is supplied but accountKeysData.IsV2Encryption() is false, the controller throws BadRequestException("AccountKeys are only supported for V2 encryption.") → HTTP 400. The AccountKeys field exclusively carries V2 (account-keys / SSO-style) encryption data; V1 keypair submissions must omit it.
Source
Thrown at src/Api/Auth/Controllers/AccountsController.cs:527
public async Task<KeysResponseModel> PostKeys([FromBody] KeysRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!string.IsNullOrWhiteSpace(user.PrivateKey) || !string.IsNullOrWhiteSpace(user.PublicKey))
{
throw new BadRequestException("User has existing keypair");
}
if (model.AccountKeys != null)
{
var accountKeysData = model.AccountKeys.ToAccountKeysData();
if (!accountKeysData.IsV2Encryption())
{
throw new BadRequestException("AccountKeys are only supported for V2 encryption.");
}
// A client that predates the key id field sends none. The account then picks one up from
// the backfill endpoint on a later sync rather than here.
var userKeyId = KeyId.FromHexEncodedString(model.UserKeyId);
var updateUserDataTasks = userKeyId == null
? null
: new UpdateUserData[] { _userRepository.SetUserKeyId(user.Id, userKeyId) };
await _userRepository.SetV2AccountCryptographicStateAsync(user.Id, accountKeysData,
updateUserDataTasks);
return new KeysResponseModel(accountKeysData, user.Key);
}
else
{
// Todo: Drop this after a transition period. This will drop no-account-keys requests.
// The V1 check in the other branch should persist
// https://bitwarden.atlassian.net/browse/PM-27329
await _userService.SaveUserAsync(model.ToUser(user));View on GitHub (pinned to e93b962371)
Solutions
- Ensure AccountKeys is only populated with V2-encryption data; for V1 keypair submission omit AccountKeys entirely (the else branch handles it).
- Verify ToAccountKeysData().IsV2Encryption() returns true on the client before posting.
- Upgrade the client fully to the V2 key model so the payload is internally consistent.
Example fix
// before model.AccountKeys = legacyV1Keypair; // IsV2Encryption() == false // after — V1 path: omit AccountKeys model.AccountKeys = null; model.PublicKey = pub; model.PrivateKey = priv;
Defensive patterns
Strategy: type-guard
Validate before calling
// Only send AccountKeys when it represents V2 encryption
if (model.AccountKeys != null && !model.AccountKeys.ToAccountKeysData().IsV2Encryption())
model.AccountKeys = null; // fall back to V1 keypair path Type guard
static bool IsV2(AccountKeysData? d) => d is not null && d.IsV2Encryption();
Try / catch
try { await client.PostAsync("accounts/keys", content); }
catch (BadRequestException ex) when (ex.Message.Contains("V2 encryption"))
{ /* rebuild payload: omit AccountKeys or supply real V2 data */ } Prevention
- Keep the key payload internally consistent (V1 fields vs V2 AccountKeys).
- Omit AccountKeys for V1 keypair submission.
- Verify IsV2Encryption() client-side before posting.
When it happens
Trigger: A client sends an AccountKeys payload whose structure does not represent V2 encryption (e.g. a legacy/V1 keypair wrapped in the AccountKeys field), or passes the V1 public/private keypair through the V2 field.
Common situations: Mixed client version sending both V1 keypair fields and a non-V2 AccountKeys object; serialization bug putting PublicKeyEncryptionKeyPairData in the wrong place; client upgraded partially and sends an inconsistent payload.
Related errors
- The model state is invalid.
- User has existing keypair
- All existing trusted devices must be included in the rotatio
- All existing emergency access keys must be included in the r
- All existing folders must be included in the rotation.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/f9db62f55f51eb1e.
Report an issue: GitHub.