passbolt/passbolt_api · error · BadRequestException

The entity must be an array.

Error message

The entity must be an array.

What it means

MetadataBatchUpdateValidationService::validateMany processes batch metadata update payloads and requires every entry in the request array to be an associative array (an entity object with at least an `id`). If any element is a scalar or null, it throws this BadRequestException. It is the first guard of the batch update validation pipeline.

Solutions

  1. Send each batch entry as an object containing at least the `id` key: [{"id": "<uuid>", ...}, ...].
  2. Validate the payload shape client-side, ensuring every element is_array before the request.
  3. Fix JSON serialization so no element collapses to a scalar/null (e.g. empty objects encoded as empty arrays or null).
  4. Wrap validateMany and return a 400 identifying the offending index to callers.

Example fix

// before
["<uuid-1>", "<uuid-2>"]

// after
[{"id": "<uuid-1>", "metadata_key_id": "<key-uuid>"}, {"id": "<uuid-2>", "metadata_key_id": "<key-uuid>"}]
Defensive patterns

Strategy: validation

Validate before calling

if (!is_array($batch)) {
    throw new InvalidArgumentException('Batch body must be a list');
}
foreach ($batch as $i => $entry) {
    if (!is_array($entry) || !isset($entry['id'])) {
        throw new InvalidArgumentException("Batch entry $i must be an object with an id");
    }
}

Type guard

function isBatchEntityList(mixed $v): bool {
    return is_array($v) && array_reduce($v, fn($ok, $e) => $ok && is_array($e) && isset($e['id']), true);
}

Try / catch

try {
    $data = $service->validateMany($requestData);
} catch (BadRequestException $e) {
    return $this->getResponse()->withStatus(400, 'Each batch entry must be an object');
}

Prevention

When it happens

Trigger: Batch metadata update endpoints (e.g. PUT /metadata/resources or /metadata/folders batch) receiving a request array where an element is a string/number/null instead of an object like {"id": "<uuid>", ...}; calling validateMany directly with malformed arrays.

Common situations: Clients sending a flat list of ID strings instead of objects; JSON payloads where one row was serialized incorrectly; automation scripts building the batch list by joining IDs rather than objects.

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/44cfcaaeafaae119. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Model/Validation/MetadataBatchUpdateValidationService.php:57

    /**
     * Get the validation form
     *
     * @return \Passbolt\Metadata\Form\Upgrade\MetadataBatchUpgradeForm
     */
    abstract public function getForm(): MetadataBatchUpgradeForm;

    /**
     * @param array $requestData Request data.
     * @return array
     * @throws \Cake\Http\Exception\BadRequestException If data is invalid.
     * @throws \App\Error\Exception\CustomValidationException If data is invalid.
     * @throws \Cake\Http\Exception\NotFoundException If one or more resources are not found.
     */
    public function validateMany(array $requestData): array
    {
        foreach ($requestData as $values) {
            if (!is_array($values)) {
                throw new BadRequestException(__('The entity must be an array.'));
            }

            $id = $values['id'] ?? null;
            if (!Validation::uuid($id)) {
                throw new BadRequestException(__('The identifier should be a valid UUID.'));
            }
        }

        $entityIds = Hash::extract($requestData, '{n}.id');
        $this->entities = $this->queryEntitiesFromIds($entityIds)->all()->toArray();
        // Re-arrange entities array to set key as identifier and value as entity object to easily find it
        $this->entities = Hash::combine($this->entities, '{n}.id', '{n}');

        $data = [];
        $errors = [];
        foreach ($requestData as $i => $entity) {
            $entityId = $entity['id'];
            if (!array_key_exists($entityId, $this->entities)) {

View on GitHub (pinned to 31c1bbc10f)