passbolt/passbolt_api · error · BadRequestException
The account recovery request status is not approved.
Error message
The account recovery request status is not approved.
What it means
Thrown during account recovery setup/recover completion when the pending account recovery request associated with the authentication token exists but its status is not 'approved'. Passbolt only allows completing a recovery workflow once an administrator has approved the request, so any other status (pending, rejected, etc.) is rejected as a bad request.
Solutions
- Have an administrator review and approve the account recovery request (Admin Workspace > Account Recovery) so its status becomes 'approved', then retry the complete call
- Restart the recovery flow from the beginning to generate a fresh request, and ensure it is approved before completing
- Verify the organization account recovery policy is enabled and the request corresponds to the current user/token pair (stale tokens reference old requests)
- Check the account_recovery_requests table status for the user to confirm the workflow state before debugging client code
Example fix
// before: completing with a pending request await passbolt.setupRecoverComplete(userId, tokenId); // after: ensure the request is approved first (admin action), then retry; // client can pre-check via GET /account-recovery/requests for status === 'approved'
Defensive patterns
Strategy: try-catch
Validate before calling
const req = await fetch(`/account-recovery/requests?user_id=${userId}`);
const status = (await req.json())?.status;
if (status !== 'approved') throw new Error('Request not approved yet'); Type guard
const isApproved = (r) => r && typeof r.status === 'string' && r.status === 'approved';
Try / catch
try {
await passbolt.accountRecovery.complete(userId, tokenId);
} catch (e) {
if (e.code === 400 && /not approved/.test(e.message)) {
notifyAdminForApproval();
} else { throw e; }
} Prevention
- Always complete the admin approval step before calling the complete endpoint
- Surface request status in the UI and block the complete call until 'approved'
- Restart the recovery flow instead of reusing old tokens
- Watch the account_recovery_requests table status during integration testing
When it happens
Trigger: Calling the setup/recover complete endpoint (POST /setup/recover/complete/<userId>/<tokenId> or setup complete) where the AccountRecoveryRequests row linked to the user still has status other than 'approved' — e.g. the admin never approved the request, or the request was created/recreated after approval, or a stale token references a superseded request.
Common situations: Admin approval workflow not finished before the user completes setup; user re-initiates account recovery (creating a new pending request) but the browser still holds an old approved token flow; environment restored from backup with reset request statuses; testing the recover flow without running the organization recovery settings approval step.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Account recovery is disabled. Key backup is not supported.
- Account recovery is mandatory. Please provide the mandatory…
- Could not save the account recovery setting.
- Account recovery case must be a string.
- Account recovery is disabled.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/7a6386f16a8a089f.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/AccountRecovery/src/Service/Setup/AccountRecoveryRecoverCompleteService.php:86
'AuthenticationTokens.token' => $this->request->getData('authenticationtoken.token'),
]);
})
->where([
'AccountRecoveryRequests.id' => $requestId,
'AccountRecoveryRequests.user_id' => $token->user_id,
])
->contain('AccountRecoveryResponses', function (Query $query) {
return $query
->select([
'AccountRecoveryResponses.id',
'AccountRecoveryResponses.account_recovery_request_id',
'AccountRecoveryResponses.data',
]);
})
->firstOrFail();
if (!$request->isApproved()) {
throw new BadRequestException(__('The account recovery request status is not approved.'));
}
$this->AuthenticationTokens->hasOne('Passbolt/AccountRecovery.AccountRecoveryRequests');
$request->setAccess([
'status',
'modified_by',
'account_recovery_responses',
], true);
$request->status = AccountRecoveryRequest::ACCOUNT_RECOVERY_REQUEST_COMPLETED;
$request->modified_by = $token->user_id;
foreach ($request->account_recovery_responses as $response) {
$response->setAccess(['data', 'modified_by'], true);
$response->data = null;
$response->modified_by = $token->user_id;
}
$request->setDirty('account_recovery_responses');
$token->set('account_recovery_request', $request);View on GitHub (pinned to 31c1bbc10f)