passbolt/passbolt_api · error · BadRequestException

The metadata private key data must be an array.

Error message

The metadata private key data must be an array.

What it means

MetadataPrivateKeysCreateManyDto's constructor iterates the request data and requires every element to be an array (one metadata-private-key entry per user). If any element is a scalar or null, it throws this BadRequestException. It guards against malformed batch payloads when metadata private keys are shared with users.

Solutions

  1. Ensure the request data is a numerically indexed array where every element is an associative array containing metadata_key_id, user_id, and data.
  2. Validate/normalize the payload client-side before the call, rejecting or skipping non-array entries.
  3. Check the JSON body nesting: it must be a list of objects, not a single object or scalar.
  4. Wrap the DTO construction and surface a clear 400 message indicating which entry index was malformed.

Example fix

// before
{"metadata_key_id": "<uuid>", "user_id": "<uuid>", "data": "<armored>"}

// after
[{"metadata_key_id": "<uuid>", "user_id": "<uuid>", "data": "<armored>"}]
Defensive patterns

Strategy: validation

Validate before calling

$privateKeys = $requestData['metadata_private_keys'] ?? [];
if (!is_array($privateKeys) || empty($privateKeys)) {
    throw new InvalidArgumentException('metadata_private_keys must be a non-empty list');
}
foreach ($privateKeys as $i => $entry) {
    if (!is_array($entry)) {
        throw new InvalidArgumentException("Entry $i must be an array");
    }
}

Type guard

function isMetadataPrivateKeyList(mixed $v): bool {
    return is_array($v) && array_reduce($v, fn($ok, $e) => $ok && is_array($e), true);
}

Try / catch

try {
    $dto = new MetadataPrivateKeysCreateManyDto($requestData);
} catch (BadRequestException $e) {
    return $this->getResponse()->withStatus(400, 'Each metadata private key entry must be an object');
}

Prevention

When it happens

Trigger: Calls that build MetadataPrivateKeysCreateManyDto (metadata private keys create-many endpoints) passing a payload where an entry under the collection key is a string, integer, or null instead of an associative array with metadata_key_id/user_id/data keys.

Common situations: Client sends the private keys object itself instead of an array of objects (e.g. {"0": {...}} vs. a single object); JSON bodies where a list element is omitted and defaults to null; integration scripts hand-building the payload with wrong nesting.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Model/Dto/MetadataPrivateKeysCreateManyDto.php:36

use Cake\Http\Exception\BadRequestException;

class MetadataPrivateKeysCreateManyDto
{
    /**
     * @var array
     */
    private array $data = [];

    /**
     * @param array $requestData Request data to convert into DTO.
     * @return void
     */
    public function __construct(array $requestData)
    {
        foreach ($requestData as $data) {
            if (!is_array($data)) {
                throw new BadRequestException(__('The metadata private key data must be an array.'));
            }

            $this->data[] = [
                'metadata_key_id' => $data['metadata_key_id'] ?? null,
                'user_id' => $data['user_id'] ?? null,
                'data' => $data['data'] ?? null,
            ];
        }
    }

    /**
     * @return array
     */
    public function getData(): array
    {
        return $this->data;
    }
}

View on GitHub (pinned to 31c1bbc10f)