passbolt/passbolt_api · error · App\Error\Exception\FormValidationException

Could not validate user data.

Error message

Could not validate user data.

What it means

editPost runs the submitted data through UsersEditForm; if form->execute() fails (field-level validation: email format, profile fields, role assignment rules, gpg key data, etc.), it throws FormValidationException 'Could not validate user data.' with the form's errors attached to the response.

Solutions

  1. Read the 400 response body: FormValidationException exposes per-field errors under errors
  2. Fix the flagged fields (valid email, role id, profile names) and resubmit
  3. Send only the fields UsersEditForm accepts; check the form schema for your server version
  4. Test the payload against the dry-run/edit form rules before calling in production

Example fix

// before
PUT /users/<id>.json {"role_id": "admin"} // string, not UUID
// after
PUT /users/<id>.json {"role_id": "0d3f5d10-9b11-5b3d-a1d2-5f0e8d3f0b1e", "profile": {"first_name": "Ada", "last_name": "Lovelace"}}
Defensive patterns

Strategy: validation

Validate before calling

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const errors = {};
if (payload.email && !/^[^@\s]+@[^@\s]+$/.test(payload.email)) errors.email = 'invalid';
if (payload.role_id && !UUID_RE.test(payload.role_id)) errors.role_id = 'must be a UUID';
if (payload.profile?.first_name && !payload.profile.first_name.trim()) errors.first_name = 'required';

Try / catch

try { await api.editUser(id, payload); } catch (e) { if (e.status === 400 && e.body?.errors) { showFieldErrors(e.body.errors); return; } throw e; }

Prevention

When it happens

Trigger: PUT /users/<id>.json with invalid payload: malformed email, unknown role id, invalid profile first/last name, extra forbidden fields, or wrong types.

Common situations: Client sending role changes the form disallows; omitting required profile fields; sending quoted strings where ids expected; API version drift between client and server.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/Controller/Users/UsersEditController.php:69

     * @param string $id user uuid
     * @param \League\Flysystem\FilesystemAdapter $filesystemAdapter file system adapter to write the avatar in cache if saved
     * @param \App\Service\Resources\ResourcesExpireResourcesServiceInterface $resourcesExpireResourcesService Service to expire resources that were consumed by users who lost access to them.
     * @return void
     */
    public function editPost(
        string $id,
        FilesystemAdapter $filesystemAdapter,
        ResourcesExpireResourcesServiceInterface $resourcesExpireResourcesService
    ) {
        $this->assertJson();
        $data = $this->request->getData();
        $data['id'] = $id;

        $this->assertCanEdit($data);

        $form = new UsersEditForm();
        if (!$form->execute($data)) {
            throw new FormValidationException(__('Could not validate user data.'), $form);
        }

        $this->assertRequestData($data);

        // Try to find the user and validate changes it
        /** @var \App\Model\Table\UsersTable $usersTable */
        $usersTable = $this->fetchTable('Users');
        $this->Users = $usersTable;
        try {
            /** @var \App\Model\Entity\User $user */
            $user = $this->Users->findView($id, $this->User->role())->first();
        } catch (Exception $exception) {
            throw new BadRequestException(__('The user does not exist or has been deleted.'));
        }
        if (empty($user)) {
            throw new BadRequestException(__('The user does not exist or has been deleted.'));
        }
        $wasDisabledNull = is_null($user->disabled);

View on GitHub (pinned to 31c1bbc10f)