Email and password changes
Changing the email address or password takes several calls. Fluxer emails a code, you send it back with the ticket from the first call, and each step returns what the next one needs. Every route needs a user session, and bots can't use them.
On an instance where people sign in with a username (account_identity is username in the instance features), accounts have no email address. There the email change and password change routes return 400 EMAIL_UNAVAILABLE_ON_INSTANCE. Use Set a new password, Get recovery kit status and Create a recovery kit instead, which return 400 USERNAME_SIGN_IN_ONLY on an email instance.
An account linked to an instance that enforces single sign-on gets 403 SSO_REQUIRED from every email change and password change route, because the provider manages its email and sign-in.
Tickets and codes
Section titled “Tickets and codes”Keep these values out of logs and URLs, because anyone who has one can continue the change.
| Value | Description |
|---|---|
| ticket | A UUID from the first call of a flow. It belongs to one account and stops working once the flow completes |
| code | An emailed code like ABCD-1234, valid for 10 minutes. Send it exactly as written, in uppercase with the hyphen |
| original_proof | A UUID that proves the original address step, needed to request and verify the new address |
| verification_proof | A UUID that proves the password change code, needed to complete the change |
| email_token | A UUID for one verified new address, valid for 30 minutes and usable once |
You can resend a code 30 seconds after the previous send, and every email also counts against a per-account limit (see Rate limits). A bad ticket, code, proof or token returns 400 INVALID_FORM_BODY. The validation error entry has the specific code, such as INVALID_VERIFICATION_CODE or VERIFICATION_CODE_EXPIRED, and its path is the field.
Email change steps
Section titled “Email change steps”An email change goes through these steps in order. When require_original is false, the ticket skips the first verify step and Start email change returns original_proof right away.
| Step | Call | Result |
|---|---|---|
| 1 | Start email change | A ticket and a code to the current address |
| 2 | Verify original email | The original address proof |
| 3 | Request new email | A code sent to the new address |
| 4 | Verify new email | The email token |
| 5 | Apply email change | The updated user |
Calling a step out of order returns ORIGINAL_EMAIL_MUST_BE_VERIFIED_FIRST, ORIGINAL_VERIFICATION_NOT_REQUIRED, ORIGINAL_EMAIL_ALREADY_VERIFIED or NO_NEW_EMAIL_REQUESTED.
Email change start object
Section titled “Email change start object”A new email change ticket.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket for the later steps |
| require_original | boolean | Whether you must verify a code sent to the current address first. True when that address is verified |
| original_email | ?string | The current address on the account |
| original_proof | ?string | The proof, set only when require_original is false |
| original_code_expires_at | ?ISO8601 timestamp | When the code sent to the current address expires |
| resend_available_at | ?ISO8601 timestamp | When you can resend the code to the current address |
Example
Section titled “Example”{ "ticket": "6f1a4c9e-3d52-4a77-9b0e-2c8f5d41ab30", "require_original": true, "original_email": "old@example.com", "original_proof": null, "original_code_expires_at": "2026-03-04T18:10:00.000Z", "resend_available_at": "2026-03-04T18:00:30.000Z"}New email request object
Section titled “New email request object”A ticket with a new address and a code sent to it.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket for the later steps |
| new_email | string | The new address, trimmed |
| new_code_expires_at | ISO8601 timestamp | When the code sent to the new address expires |
| resend_available_at | ?ISO8601 timestamp | When you can resend the code |
Original email verification object
Section titled “Original email verification object”Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| original_proof | string | The proof for the later email change steps |
New email verification object
Section titled “New email verification object”Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| email_token | string | The token Apply email change uses |
Password change start object
Section titled “Password change start object”A new password change ticket.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket for the later steps |
| code_expires_at | ISO8601 timestamp | When the emailed code expires |
| resend_available_at | ?ISO8601 timestamp | When you can resend the code |
Password verification object
Section titled “Password verification object”Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| verification_proof | string | The proof Complete password change needs |
Password change completion object
Section titled “Password change completion object”The new session after a password change. Fluxer ends every session on the account, including the one that made the request, and you send later requests with this token. Modify current user adds the same fields when it changes the password.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| token | string | The token for the new session |
| auth_session_id_hash | string | The base64url hash of the new session ID |
Recovery kit status object
Section titled “Recovery kit status object”Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| has_recovery_kit | boolean | Whether the account has a recovery kit |
| created_at | ?ISO8601 timestamp | When the current kit was created |
Recovery kit object
Section titled “Recovery kit object”A new recovery kit, whose key Recover an account uses to set a new password without signing in.
Structure
Section titled “Structure”| Field | Type | Description |
|---|---|---|
| recovery_key | string | The key, 8 groups of 4 Crockford base32 characters joined by - |
| created_at | ISO8601 timestamp | When the kit was created |
Start email change
Section titled “Start email change”POST/v1/users/@me/email-change/startCreates an email change ticket and, when the current address is verified, emails it a code. A claimed account with no email address gets MUST_HAVE_EMAIL_TO_CHANGE_IT. An instance that can't send email returns 400 EMAIL_CODES_UNAVAILABLE here and from Resend original email code.
JSON body
Section titled “JSON body”The body is empty or an empty object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | email change start object | The ticket was created |
The rate limit is 10 requests per minute, on the user:email_change:start bucket.
Resend original email code
Section titled “Resend original email code”POST/v1/users/@me/email-change/resend-originalSends a new code to the current address and makes the previous code invalid.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start email change (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The code was sent |
The rate limit is 10 requests per minute, on the user:email_change:resend_original bucket.
Verify original email
Section titled “Verify original email”POST/v1/users/@me/email-change/verify-originalChecks the code sent to the current address and returns a proof, and calling it again on the same ticket returns the same proof.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start email change (1-256 characters) |
| code | string | The code sent to the current address (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | original email verification object | The current address is verified |
The rate limit is 20 requests per minute, on the user:email_change:verify_original bucket.
Request new email
Section titled “Request new email”POST/v1/users/@me/email-change/request-newSets the new address on the ticket and emails it a code. Calling it again with another address replaces the address on the ticket. The new address must differ from the current one, use a domain the instance allows and not belong to another account.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start email change (1-256 characters) |
| new_email | string | The new address (1-254 characters) |
| original_proof | string | The proof from Start email change or Verify original email (1-256 characters) |
| new_password? | string | A password to check against known breached passwords. Not saved (8-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | new email request object | The code was sent to the new address |
The rate limit is 10 requests per minute, on the user:email_change:request_new bucket.
Resend new email code
Section titled “Resend new email code”POST/v1/users/@me/email-change/resend-newSends a new code to the new address and makes the previous code invalid.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start email change (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The code was sent |
The rate limit is 10 requests per minute, on the user:email_change:resend_new bucket.
Verify new email
Section titled “Verify new email”POST/v1/users/@me/email-change/verify-newChecks the code sent to the new address, completes the ticket and returns an email token. The account email doesn't change until you call Apply email change.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start email change (1-256 characters) |
| code | string | The code sent to the new address (1-256 characters) |
| original_proof | string | The proof from Start email change or Verify original email (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | new email verification object | The new address is verified |
The rate limit is 20 requests per minute, on the user:email_change:verify_new bucket.
Apply email change
Section titled “Apply email change”POST/v1/users/@me/email-change/applySets the verified address on the account and returns the updated user. A claimed account needs sudo mode, and an unclaimed account doesn't.
The token fails with EMAIL_TOKEN_EXPIRED after 30 minutes and INVALID_EMAIL_TOKEN once used.
JSON body
Section titled “JSON body”The body extends the sudo verification object with email_token.
| Field | Type | Description |
|---|---|---|
| email_token | string | The token from Verify new email (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | user object | The new address was set |
Fluxer sends User Update and emails the previous address, if there was one, a link to revert the change. Sessions and the password don't change.
The rate limit is 20 requests per minute, on the user:email_change:apply bucket.
Bounced email recovery
Section titled “Bounced email recovery”When mail to an account's address bounces, the user object has email_bounced set to true. The three routes below set a new address without the original address step or sudo mode. Other accounts use the normal email change flow.
Request replacement email for bounced address
Section titled “Request replacement email for bounced address”POST/v1/users/@me/email-change/bounced/request-newCreates a ticket, sets the new address on it and emails it a code. The address is checked the same way as in Request new email.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| new_email | string | The new address (1-254 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | new email request object | The code was sent to the new address |
The rate limit is 10 requests per minute, on the user:email_change:bounced:request_new bucket.
Resend replacement email code
Section titled “Resend replacement email code”POST/v1/users/@me/email-change/bounced/resend-newSends a new code to the new address and makes the previous code invalid.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Request replacement email for bounced address (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The code was sent |
The rate limit is 10 requests per minute, on the user:email_change:bounced:resend_new bucket.
Verify replacement email for bounced address
Section titled “Verify replacement email for bounced address”POST/v1/users/@me/email-change/bounced/verify-newChecks the code and sets the new address on the account in the same call. Returns the updated user with email_bounced set to false, and Fluxer sends User Update.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Request replacement email for bounced address (1-256 characters) |
| code | string | The code sent to the new address (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | user object | The new address was set |
The rate limit is 20 requests per minute, on the user:email_change:bounced:verify_new bucket.
Start password change
Section titled “Start password change”POST/v1/users/@me/password-change/startCreates a password change ticket and emails a code to the account address. An account with no email address gets MUST_HAVE_EMAIL_TO_CHANGE_IT.
JSON body
Section titled “JSON body”The body is empty or an empty object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | password change start object | The code was sent |
The rate limit is 10 requests per minute, on the user:password_change:start bucket.
Resend password change code
Section titled “Resend password change code”POST/v1/users/@me/password-change/resendSends a new code and makes the previous code invalid.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start password change (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 204 | empty | The code was sent |
The rate limit is 10 requests per minute, on the user:password_change:resend bucket.
Verify password change code
Section titled “Verify password change code”POST/v1/users/@me/password-change/verifyChecks the emailed code and returns a proof, and calling it again on the same ticket returns the same proof.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start password change (1-256 characters) |
| code | string | The emailed code (1-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | password verification object | The code is correct |
The rate limit is 20 requests per minute, on the user:password_change:verify bucket.
Complete password change
Section titled “Complete password change”POST/v1/users/@me/password-change/completeSets the new password and returns a new session. An account with TOTP or a WebAuthn credential also needs sudo mode with that factor. A password found in known breaches fails with PASSWORD_IS_TOO_COMMON.
JSON body
Section titled “JSON body”The body extends the sudo verification object with these fields.
| Field | Type | Description |
|---|---|---|
| ticket | string | The ticket from Start password change (1-256 characters) |
| verification_proof | string | The proof from Verify password change code (1-256 characters) |
| new_password | string | The new password (8-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | password change completion object | The password was changed |
Fluxer sends Auth Session Change with the old and new session ID hashes.
The rate limit is 10 requests per minute, on the user:password_change:complete bucket.
Set a new password
Section titled “Set a new password”POST/v1/users/@me/passwordChanges the password on a username instance after sudo mode and returns a new session. It also deletes the account's recovery kit. A password found in known breaches fails with PASSWORD_IS_TOO_COMMON, and an SSO account with no password gets PASSWORD_NOT_SET. An instance that enforces single sign-on returns 403 SSO_REQUIRED.
JSON body
Section titled “JSON body”The body extends the sudo verification object with new_password.
| Field | Type | Description |
|---|---|---|
| new_password | string | The new password (8-256 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | password change completion object | The password was changed |
Fluxer sends Auth Session Change with the old and new session ID hashes.
The rate limit is 10 requests per minute, on the user:password:update bucket.
Get recovery kit status
Section titled “Get recovery kit status”GET/v1/users/@me/recovery-kitReturns whether the account has a recovery kit.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | recovery kit status object | The status was returned |
The rate limit is 20 requests per minute, on the user:recovery_kit:get bucket.
Create a recovery kit
Section titled “Create a recovery kit”POST/v1/users/@me/recovery-kitCreates a recovery kit after sudo mode and makes the previous key stop working. Show the key to the person before you discard the response, because Fluxer never returns it again. An SSO account with no password gets PASSWORD_NOT_SET, and an instance that enforces single sign-on returns 403 SSO_REQUIRED.
JSON body
Section titled “JSON body”The body is the sudo verification object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | recovery kit object | The kit was created |
The rate limit is 6 requests per minute, on the user:recovery_kit:create bucket.