passbolt/passbolt_api · error · BadRequestException

The metadata key is already shared with the user.

Error message

The metadata key is already shared with the user.

What it means

After existence checks, assertRequestSanity queries MetadataPrivateKeysTable for an existing private key matching the metadata key and target (user, or server when user_id is null). If one already exists, the duplicate share attempt is rejected with a BadRequestException.

Solutions

  1. Check for the existing metadata private key first (GET the key's private keys) and skip creation if present
  2. Make the client operation idempotent: treat this 400 as 'already done' where appropriate
  3. For re-sharing after rotation, delete/replace the old private key record or use the rotate flow instead of create

Example fix

// before
create($uac, $keyId, $data); // retried blindly
// after
if (!$privateKeysService->existsForUser($keyId, $userId)) {
    create($uac, $keyId, $data);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await client.metadataPrivateKeys.list(keyId); if (existing.some(pk => pk.user_id === userId || pk.user_id === null)) return;

Type guard

const alreadyShared = (existing, userId) => existing.some(pk => (userId ? pk.user_id === userId : pk.user_id === null));

Try / catch

catch (e) { if (e.response?.status === 400 && /already shared/.test(e.response?.body?.message)) { return { status: 'already-shared' }; } throw e; }

Prevention

When it happens

Trigger: POSTing a metadata private key for a user (or the server) that already has one for the same metadata key id — including retries of a successful earlier call.

Common situations: Non-idempotent retry logic resending the same share; two admins sharing the same key concurrently; client not updating its local copy of who has the key.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/e385d1d94af15ca9. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Service/MetadataPrivateKeysCreateService.php:156

        } catch (RecordNotFoundException $exception) {
            throw new NotFoundException(__('The metadata key does not exist or has been deleted.'));
        }

        // Assert private key does not already exist for the user/server
        /** @var \Passbolt\Metadata\Model\Table\MetadataPrivateKeysTable $metadataPrivateKeysTable */
        $metadataPrivateKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataPrivateKeys');
        $metadataPrivateKey = $metadataPrivateKeysTable->find()
            ->where(['metadata_key_id' => $metadataKeyId])
            ->where(function (QueryExpression $exp) use ($data) {
                if (isset($data['user_id'])) {
                    return $exp->eq('user_id', $data['user_id']);
                }

                return $exp->isNull('user_id');
            })
            ->first();
        if (!empty($metadataPrivateKey)) {
            throw new BadRequestException(__('The metadata key is already shared with the user.'));
        }
    }

    /**
     * @param \App\Utility\UserAccessControl $uac User access control.
     * @param \Passbolt\Metadata\Model\Dto\MetadataPrivateKeysCreateManyDto $dto User provided data.
     * @return void
     * @throws \Cake\Http\Exception\BadRequestException if the data is invalid
     * @throws \App\Error\Exception\ValidationException if the data does not validate
     * @throws \Cake\Http\Exception\InternalErrorException if data could not be saved because of an internal issue
     * @throws \Cake\Http\Exception\NotFoundException if the key was not found
     */
    public function createMany(UserAccessControl $uac, MetadataPrivateKeysCreateManyDto $dto): void
    {
        $uac->assertIsAdmin();
        if (empty($dto->getData())) {
            return;
        }

View on GitHub (pinned to 31c1bbc10f)