Two pairs of mutations let persons (and administrators) update profile and credentials:
| Self-service | Admin |
|---|---|
changeMyProfile(email, name) | changeProfile(personId, email, name) |
changeMyPassword(currentPassword, newPassword) | changePassword(personId, password) |
The self-service variants require the caller to be authenticated as the person being changed; the admin variants take a personId and are gated by per-target permission checks.
Self-service
Change my profile
mutation {
changeMyProfile(email: "[email protected]", name: "Alice Example") {
ok
error { code developerMessage }
}
}
Pass either field individually — omitted fields are left unchanged. Setting name: "" clears the name.
When emailChange.requireVerification is enabled (it is off by default) — or the account was created under sign-up verification, which implies it — changing the e-mail does not apply immediately: changeMyProfile returns ok: true, mails a confirmation token to the new address, and keeps the old address active until the user confirms via confirmEmailChange. A name change passed in the same call is still applied right away. When neither applies (a deployment with no verification at all), addresses swap immediately as before. In that direct case the change also clears the verified status of the address, since the new one is unproven.
Errors:
| Code | Cause |
|---|---|
NOT_A_PERSON | Caller is authenticated via a permanent API key, not a person. |
INVALID_EMAIL_FORMAT | Email failed format validation. |
EMAIL_ALREADY_EXISTS | Email is already used by another person. |
RATE_LIMIT_EXCEEDED | (since 2.2) Per-recipient backoff on confirmation mails for a verified e-mail change. |
An immediate (unverified) email change records email_change in the audit log; a verified change records email_change_init / email_change_complete instead.
Change my password
mutation {
changeMyPassword(currentPassword: "…", newPassword: "…") {
ok
error {
code
weakPasswordReasons
developerMessage
}
}
}
The caller must supply their current password — this protects against session hijack where an attacker holding a session token tries to lock out the user.
Errors:
| Code | Cause |
|---|---|
NOT_A_PERSON | Caller is authenticated via a permanent API key. |
NO_PASSWORD_SET | The person has no password (IDP-only / passwordless-only). Use password reset to set the first password. |
INVALID_PASSWORD | currentPassword is wrong. |
TOO_WEAK | newPassword failed password policy. weakPasswordReasons[] carries reasons including COMPROMISED (since 2.2). |
A successful change records password_change in the audit log.
Admin
Change someone's profile
mutation {
changeProfile(personId: "…", email: "[email protected]", name: "Alice") {
ok
error { code developerMessage }
}
}
Gated by PERSON_CHANGE_PROFILE against the target's roles — a PROJECT_ADMIN can change profiles of persons whose roles do not exceed their own; SUPER_ADMIN is unrestricted.
Errors: PERSON_NOT_FOUND, INVALID_EMAIL_FORMAT, EMAIL_ALREADY_EXISTS.
The admin changeProfile is never routed through e-mail-change verification — the new address is applied immediately regardless of emailChange.requireVerification. Only the self-service changeMyProfile goes through confirmation.
Set someone's password
mutation {
changePassword(personId: "…", password: "…") {
ok
error {
code
weakPasswordReasons
developerMessage
}
}
}
Gated by PERSON_CHANGE_PASSWORD against the target's roles. No current-password check — the admin is trusted by policy.
Use cases:
- Forced credential rotation after a known compromise.
- Setting a temporary password before invoking
forceSignOutPersonto lock the person out of their existing sessions. - Bulk migration scripts.
The new password is validated against the active password policy just like the self-service path. Records password_change in the audit log.
When the person has no password
changeMyPassword returns NO_PASSWORD_SET for persons created via IDP or passwordless-only flows. To set an initial password, either:
- Trigger the public password reset flow.
- Have an admin call
changePassworddirectly.