passbolt/passbolt_api · error · App\Error\Exception\CustomValidationException
Could not validate public key data.
Error message
Could not validate public key data.
What it means
This CustomValidationException is raised during an enabled-to-enabled policy change without key rotation: the request omitted 'public_key_id', so the service cannot determine which existing organization public key to reuse. The message 'Could not validate public key data.' carries a nested validation error (_required) indicating the organization public key identifier is required.
Solutions
- Add the current organization public key's id as 'public_key_id' in the request payload so the existing key is reused.
- Fetch the current policy via GET /account-recovery/organization-policies.json to obtain the correct public_key_id before submitting.
- If a key rotation was intended instead, provide both the new key and the revoked key rather than relying on public_key_id.
- Update the client library/UI to always include public_key_id on non-rotation policy updates.
Example fix
// before (policy change without key reference)
await passbolt.setAccountRecoveryOrganizationPolicy({
policy: 'opt-in'
});
// after (include current public_key_id to reuse existing key)
await passbolt.setAccountRecoveryOrganizationPolicy({
policy: 'opt-in',
public_key_id: currentPolicy.account_recovery_organization_public_key.id
}); Defensive patterns
Strategy: validation
Validate before calling
if (!payload.account_recovery_organization_public_key && !payload.account_recovery_organization_revoked_key && !payload.public_key_id) {
throw new Error('Policy-only change requires public_key_id of the current organization key.');
} Type guard
function canReuseExistingKey(p) {
return typeof p.public_key_id === 'string' && p.public_key_id.length > 0;
} Try / catch
try {
await passbolt.setAccountRecoveryOrganizationPolicy(payload);
} catch (e) {
if (e.status === 400 && /Could not validate public key data/.test(e.body ?? e.message) && e.body?.public_key_id?._required) {
const current = await passbolt.getAccountRecoveryOrganizationPolicy();
payload.public_key_id = current.account_recovery_organization_public_key.id;
return retry(payload);
}
throw e;
} Prevention
- Always fetch the current policy before a non-rotation update and carry public_key_id through.
- Include public_key_id in any client model representing the organization policy settings form.
- Distinguish clearly in code between rotation requests and reuse-key requests.
When it happens
Trigger: Policy-only change (no new key, no revoked key) where the payload lacks both key rotation fields AND 'public_key_id'. Raised in the else branch of AccountRecoveryOrganizationPolicySetService::set() at line 118 when !isset($newPolicy->public_key_id).
Common situations: An admin changes the policy (e.g. mandatory to opt-in) with a minimal payload that omits the current public_key_id; a client library that only sends the policy field on updates; older client versions built before the public_key_id requirement; hand-crafted curl requests copied from enable examples.
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
- Could not validate response data.
- Invalid request. New key or passwords are not required.
- Invalid request. Private key or password are missing.
- An authentication token should be provided.
- Could not save the account recovery private key.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/64dd94131aab01e1.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php:118
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.'));
}
// assert passwords backups format and numbers
$passwords = $this->buildPasswordEntitiesFromDataOrFail($uac, $newKey);
}
$newPolicy->account_recovery_organization_public_key = $newKey;
} else {
// If key is not changing reuse the old one
if (!isset($newPolicy->public_key_id)) {
throw new CustomValidationException(__('Could not validate public key data.'), [
'public_key_id' => [
'_required' => __('An organization public key is required.'),
],
]);
} else {
if ($newPolicy->public_key_id !== $this->getCurrentPolicyEntity()->public_key_id) {
throw new CustomValidationException(__('Could not validate public key data.'), [
'public_key_id' => [
'notCurrentPublicKeyId' => __('The public_key_id must match current policy public_key_id.'),
],
]);
}
}
}
// save new key and disable previous key and backups if any
return $this->updatePolicy($uac, $newPolicy, $oldKey, $newKey, $passwords);
}View on GitHub (pinned to 31c1bbc10f)