Every one of these is contract: the UI must render each, not just the happy path. This is how a reset that mailed into the void, and a policy rejection with no message, get designed out before build.
Submitting · request in flight
Expired or invalid token · reset-confirm 400
This reset link has expired
Links are single-use and expire after 30 minutes. Request a new one to continue.
Password policy rejection · 400
That password is too common. Use at least 12 characters and avoid words like "password".
Definite failure · server answered with a 4xx rejection (e.g. 429 RATE_LIMITED) · retry
We couldn't update your password
The change wasn’t saved. Your password is unchanged — try again.
Change · outcome unknown · network error or 5xx after submit · no retry
We couldn't confirm the change.
Try signing in with your new password. If that doesn't work, your old password still does.
No “Try again”: the server may have saved the new password and lost the reply, so a retry would fail on the old current password. Never say “unchanged” here, and clear both password fields.
Set-password when one exists · POST /password 409
You already have a password
Use "Change password" to update it, or reset it if you've forgotten it.
Change · wrong current password · 400 INVALID_CREDENTIALS
That's not your current password. Check it, or reset your password instead.