Update the information of a specific user.
| Permission | Description |
|---|---|
users.update_any | Update any user as an admin |
| Name | Type | Required | Description |
|---|---|---|---|
first_name | string | No | First name (max 255) |
last_name | string | No | Last name (max 255) |
email | string | No | Email (max 255, unique) |
phone | string | No | Phone number (unique, E.164 format). Operator-only in practice: there is no self-service equivalent on PUT /auth/me, because a phone number is an authentication factor and changing it needs OTP re-verification that does not exist yet. |
bank_account_number | string | No | Bank account number (max 50 characters, nullable) |
username | string | No | Username (max 255, unique; letters, digits, _ and . only: /^[a-zA-Z0-9_.]+$/) |
height | integer | No | User's height in CM |
birth_date | date | No | User's birth date |
gender | string | No | User's gender (male, female, other) |
password | string | No | New password (max 255 characters, will be hashed). Operator-only: a user changes their own password through PUT /auth/change-password, which requires the current one. |
trainer_license_image | null | No | Only null is accepted, which clears the license. Any other value is rejected with 422: uploads go through Create Media with purpose=trainer_license |
trainer_license_approved | boolean|null | No | The review decision: true approves, false rejects, null returns the user to pending |
trainer_license_rejection_reason | string|null | No | Reason shown to the user when rejected (max 1000) |
All parameters are optional. If omitted, they will not be updated.
is_trainer and is_gym_owner are not accepted here, and that is deliberate. They are the user's own statement about which parts of the platform apply to them, so they are settable only on PUT /auth/me. Sending them to this endpoint has no effect: they are silently ignored, not rejected, because they are not operator fields at all. This is the mirror image of phone above: each endpoint deliberately owns the fields that belong to it. Neither flag authorizes anything, so an operator has no reason to need them.
Setting trainer_license_approved to a non-null value that differs from the user's current decision sends them a TrainerLicenseReviewedNotification (see Notifications). Re-sending the same decision is a no-op and notifies nobody.
Clearing trainer_license_image deletes the stored document and resets both trainer_license_approved and trainer_license_rejection_reason to null, unless the same request sets them explicitly.
Returns the updated user resource.
{
"data": { /* user resource */ }
}
See User Resource.
| Status | Description | Reference |
|---|---|---|
| 404 | User not found | Not-found error |
| 409 | Email, phone or username already in use | See below |
| 422 | Validation error | Validation error |
| 401 | Unauthorized | Authentication error |
| 403 | Forbidden | Permission error |
Checked in this order: email, then phone, then username; the first match answers. error is the localized
sentence (kept for released clients); branch on code, which never changes with the locale.
code | When |
|---|---|
email_in_use | The email belongs to another account |
phone_in_use | The phone number belongs to another account |
username_in_use | The username belongs to another account |
{
"error": "This email is already in use.",
"code": "email_in_use"
}