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
- Ensure the permissions data is a zero-indexed integer array, e.g. array_values($data) before sending.
- Send permissions as a JSON array [ {...}, {...} ] not an object { "0": {...} }.
- If rows must be addressed by id, put the id inside each row ('id' key), not as the array key.
- 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
- Build payload as a list (array_values) before sending
- Send JSON arrays, not objects, for permissions data
- Put permission ids inside rows, not as keys
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
- An array of arrays is expected.
- Only admin can create or update subscription information.
- Only administrators can delete the subscription.
- Service provider invalid.
- Service provider missing.
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)