passbolt/passbolt_api · error · BadRequestException

The permissions data array keys must be integers.

Error message

The permissions data array keys must be integers.

What it means

Thrown by PermissionsUpdatePermissionsService::updatePermissions when the permissions payload passed to PUT /permissions/{aco} has array keys that are not integers. Each permissions row must be keyed by an integer index (new permissions) or an existing permission id position so the service can diff add/update/delete operations deterministically. String keys (e.g. from JSON objects or un-serialized payloads) are rejected with 400.

Solutions

  1. Ensure the permissions data is a zero-indexed integer array, e.g. array_values($data) before sending.
  2. Send permissions as a JSON array [ {...}, {...} ] not an object { "0": {...} }.
  3. If rows must be addressed by id, put the id inside each row ('id' key), not as the array key.
  4. On the client, cast keys: $data = array_values($data); or json_encode(array_values(...))

Example fix

// before
$data = ['perm-1' => ['type' => 15, 'aro_foreign_key' => '...']];
// after
$data = [['id' => 'perm-uuid', 'type' => 15, 'aro_foreign_key' => '...']];
Defensive patterns

Strategy: validation

Validate before calling

if (!is_array($data) || array_keys($data) !== range(0, count($data) - 1)) { throw new \InvalidArgumentException('permissions data must be integer-indexed'); }

Type guard

function isIntIndexedArray(mixed $data): bool { return is_array($data) && array_is_list($data); }

Prevention

When it happens

Trigger: Sending permissions data as an associative array with string keys (e.g. ['perm-0' => [...]] or a JSON object keyed by permission id) instead of a sequential integer-indexed array; passing a decoded JSON object where an array is expected.

Common situations: Client SDKs or scripts building the payload as an object/map instead of a list; PHP json_decode with assoc on an object-shaped permissions payload; upstream refactors that wrap rows in named keys.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/Service/Permissions/PermissionsUpdatePermissionsService.php:81

     * @param array|null $data The permissions to update
     * @return \App\Model\Dto\EntitiesChangesDto
     * @throws \Cake\Http\Exception\BadRequestException If the permissions passed
     * @throws \Exception If something unexpected occurred
     */
    public function updatePermissions(
        UserAccessControl $uac,
        string $aco,
        string $acoForeignkey,
        ?array $data = []
    ): EntitiesChangesDto {
        $entitiesChanges = new EntitiesChangesDto();

        foreach ($data as $rowIndex => $row) {
            if (!is_array($row)) {
                throw new BadRequestException(__('The permissions data must be an array.'));
            }
            if (!is_int($rowIndex)) {
                throw new BadRequestException(__('The permissions data array keys must be integers.'));
            }
            $permissionId = Hash::get($row, 'id', null);

            // A new permission is provided when no id is found in the raw data.
            if (is_null($permissionId)) {
                $permission = $this->addPermission($uac, $rowIndex, $aco, $acoForeignkey, $row);
                $entitiesChanges->pushAddedEntity($permission);
            } else {
                // If a property delete is found and set to true, then delete the permission.
                // Otherwise update it.
                $permission = $this->getPermission($rowIndex, $acoForeignkey, $permissionId);
                $delete = Hash::get($row, 'delete');
                if ($delete) {
                    $permission = $this->deletePermission($permission);
                    $entitiesChanges->pushDeletedEntity($permission);
                } else {
                    $permission = $this->updatePermission($uac, $rowIndex, $permission, $row);
                    $entitiesChanges->pushUpdatedEntity($permission);

View on GitHub (pinned to 31c1bbc10f)