passbolt/passbolt_api · error · Cake\Http\Exception\BadRequestException
Invalid request. Keys are required for this change.
Error message
Invalid request. Keys are required for this change.
What it means
This BadRequestException is thrown when an enabled-to-enabled policy change request provides only one half of a key rotation: a new public key without the revoked (old) key, or a revoked key without the new key. Key rotation and policy changes (e.g. mandatory to opt-in) must be accompanied by a consistent pair of keys, or by neither when only the policy changes and the key is reused.
Solutions
- If rotating the key: provide BOTH the new armored public key and the old key marked as revoked in the same request.
- If only changing the policy (e.g. mandatory to opt-in) with no rotation: provide NEITHER key and instead pass the current public_key_id so it can be reused.
- Audit the client payload to ensure the new-key and revoked-key fields are always set together or not at all.
- Use the official passbolt client/CLI for organization policy changes instead of hand-built requests.
Example fix
// before (new key without revocation)
await passbolt.setAccountRecoveryOrganizationPolicy({
policy: 'opt-in',
account_recovery_organization_public_key: newArmoredKey
});
// after (complete rotation: new key + revoked old key)
await passbolt.setAccountRecoveryOrganizationPolicy({
policy: 'opt-in',
account_recovery_organization_public_key: newArmoredKey,
account_recovery_organization_revoked_key: oldArmoredKey
}); Defensive patterns
Strategy: validation
Validate before calling
const hasNew = !!payload.account_recovery_organization_public_key;
const hasRevoked = !!payload.account_recovery_organization_revoked_key;
if (hasNew !== hasRevoked) {
throw new Error('Key rotation requires both the new public key and the revoked key, or neither.');
} Type guard
function hasCompleteKeyPair(p) {
const hasNew = 'account_recovery_organization_public_key' in p && p.account_recovery_organization_public_key;
const hasRevoked = 'account_recovery_organization_revoked_key' in p && p.account_recovery_organization_revoked_key;
return hasNew === hasRevoked;
} Try / catch
try {
await passbolt.setAccountRecoveryOrganizationPolicy(payload);
} catch (e) {
if (e.status === 400 && /Keys are required for this change/.test(e.message)) {
console.error('Provide both new key and revoked key, or neither (policy-only change with public_key_id).');
}
throw e;
} Prevention
- Treat new key and revoked key as a single atomic unit in client code — validate their presence together before sending.
- For policy-only changes, intentionally omit both keys and send public_key_id.
- Write an integration test covering both rotation and policy-only-change payload shapes.
When it happens
Trigger: POST/PUT to the account recovery organization settings with current policy enabled and new policy enabled, where exactly one of 'account_recovery_organization_public_key' (new key) or 'account_recovery_organization_revoked_key' is present but not both. Raised in AccountRecoveryOrganizationPolicySetService::set() at line 92.
Common situations: A client implements key rotation but forgets to mark the old key as revoked; an admin wants to change the policy (mandatory to opt-in) but the client library injects a new key without the revocation record; partially migrated request builders after an API version change.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Invalid request. Passwords are required for this change.
- Invalid request. New key is required for key rotation.
- Invalid request. New key or passwords are not required.
- Invalid request. Revoked key is required for key rotation.
- Account recovery case must be a string.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/2f72b975a56426b8.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php:92
return $this->enablePolicy($uac, $newPolicy);
}
// if enabled => disabled
if ($this->isDisabling()) {
// if new key or passwords provided
if ($isNewKeyProvided || $isPrivateKeyPasswordsProvided) {
throw new BadRequestException(__('Invalid request. New key or passwords are not required.'));
}
// save new disabled policy, disable previous key and delete backups if any
return $this->disablePolicy($uac);
}
// if enabled => enabled
// e.g it's policy change like mandatory => opt-in
// and/or a possible key rotation
if (($isNewKeyProvided && !$isRevokedKeyProvided) || (!$isNewKeyProvided && $isRevokedKeyProvided)) {
throw new BadRequestException(__('Invalid request. Keys are required for this change.'));
}
// if key provided or revocation provided
$newKey = null;
$oldKey = null;
$passwords = null;
/** @psalm-suppress RedundantCondition */
if ($isNewKeyProvided && $isRevokedKeyProvided) {
// assert old and new key$newKey
$newKey = $this->buildPublicKeyEntityFromDataOrFail($uac);
$oldKey = $this->buildRevokedKeyEntityFromDataOrFail($uac);
// If some existing backups are present
// assert new backups are provided
if ($this->backupsExists()) {
if (!$isPrivateKeyPasswordsProvided) {
throw new BadRequestException(__('Invalid request. Passwords are required for this change.'));
}View on GitHub (pinned to 31c1bbc10f)