The password reset flow consists of three steps:
Request a reset token - User provides email or phone
Verify the reset token - Validate token and show masked identifier
Reset password - Submit new password with token
Step 1: Request Password Reset
Send a password reset link to the user via email or WhatsApp.
Authentication
This endpoint does not require authentication.
Request Body
User’s email address. Required if phone is not provided. Validation : Must be a valid email format.Example : [email protected]
User’s phone number. Required if email is not provided. Example : +525512345678
You must provide either email or phone, but not both.
Response
Response status, always “success”
Confirmation message indicating the reset link has been sent
Success Response Example
{
"status" : "success" ,
"message" : "Password reset link sent successfully"
}
Error Responses
Show 422 Unprocessable Entity - Validation Error
{
"message" : "The given data was invalid." ,
"errors" : {
"email" : [
"Email es requerido si no se proporciona teléfono."
]
}
}
Show 500 Internal Server Error
{
"status" : 500 ,
"message" : "An unexpected error occurred"
}
Code Example
curl -X POST https://api.sushigo.local/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"email": "[email protected] "
}'
Step 2: Verify Reset Token
Validate the reset token and retrieve the masked identifier (email or phone) associated with it.
Authentication
This endpoint does not require authentication.
Request Body
Combined reset parameter in the format: {40-char token}.{24-char selector} This token is typically extracted from the reset link URL sent to the user. Validation : Required string.Example : a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0.x1y2z3a4b5c6d7e8f9g0h1i2
Response
Response status, always “success”
The masked email or phone number associated with the reset token Example : ju***@example.com or +52***5678
Success Response Example
{
"status" : "success" ,
"masked_identifier" : "ju***@example.com"
}
Error Responses
Show 422 Unprocessable Entity - Invalid or Expired Token
{
"message" : "The given data was invalid." ,
"errors" : {
"t" : [
"Invalid or expired reset token."
]
}
}
Show 422 Unprocessable Entity - Missing Token
{
"message" : "The given data was invalid." ,
"errors" : {
"t" : [
"El token de restablecimiento es requerido."
]
}
}
Show 500 Internal Server Error
{
"status" : 500 ,
"message" : "An unexpected error occurred"
}
Code Example
curl -X POST https://api.sushigo.local/api/v1/auth/verify-reset-token \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"t": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0.x1y2z3a4b5c6d7e8f9g0h1i2"
}'
Step 3: Reset Password
Reset the user’s password using a valid token and return authentication credentials.
Authentication
This endpoint does not require authentication.
Request Body
Combined reset parameter in the format: {40-char token}.{24-char selector} Same token format as used in the verify step. Validation : Required string.Example : a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0.x1y2z3a4b5c6d7e8f9g0h1i2
New password for the user account. Validation : Minimum 8 characters, must match password_confirmation.Example : newpassword123
Confirmation of the new password. Must match the password field exactly. Validation : Required, must match password.Example : newpassword123
Response
HTTP status code (200 for success)
OAuth2 access token for authenticating subsequent requests Example : eyJ0eXAiOiJKV1QiLCJhbGc...
Token type, always “Bearer”
Array of role objects assigned to the user
Array of permission objects assigned to the user
The response structure is identical to the login endpoint - the user is automatically authenticated after a successful password reset.
Success Response Example
{
"status" : 200 ,
"data" : {
"token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoiLCJpYXQiOjE3MDk4NTYwMDAsIm5iZiI6MTcwOTg1NjAwMCwiZXhwIjoxNzQxMzkyMDAwLCJzdWIiOiIyIiwic2NvcGVzIjpbXX0..." ,
"token_type" : "Bearer" ,
"user" : {
"id" : 2 ,
"name" : "Juan Pérez" ,
"email" : "[email protected] " ,
"roles" : [
{
"id" : 2 ,
"name" : "inventory-manager"
}
],
"permissions" : [
{
"id" : 5 ,
"name" : "view-items"
}
]
}
}
}
Error Responses
Show 422 Unprocessable Entity - Invalid or Expired Token
{
"message" : "Invalid or expired reset token." ,
"errors" : {
"t" : [
"Invalid or expired reset token."
]
}
}
Show 422 Unprocessable Entity - Validation Error
{
"message" : "The given data was invalid." ,
"errors" : {
"password" : [
"La contraseña debe tener al menos 8 caracteres." ,
"La confirmación de contraseña no coincide."
]
}
}
Show 500 Internal Server Error
{
"status" : 500 ,
"message" : "An unexpected error occurred"
}
Code Example
curl -X POST https://api.sushigo.local/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"t": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0.x1y2z3a4b5c6d7e8f9g0h1i2",
"password": "newpassword123",
"password_confirmation": "newpassword123"
}'
Complete Flow Example
Here’s a complete implementation of the password reset flow:
// 1. Request password reset
async function requestPasswordReset ( email ) {
const response = await fetch ( '/api/v1/auth/forgot-password' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email })
});
const data = await response . json ();
alert ( data . message ); // "Password reset link sent successfully"
}
// 2. Verify token (when user clicks the reset link)
async function verifyResetToken ( token ) {
const response = await fetch ( '/api/v1/auth/verify-reset-token' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ t: token })
});
if ( ! response . ok ) {
throw new Error ( 'Invalid or expired token' );
}
const data = await response . json ();
return data . masked_identifier ; // "ju***@example.com"
}
// 3. Reset password
async function resetPassword ( token , password , passwordConfirmation ) {
const response = await fetch ( '/api/v1/auth/reset-password' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
t: token ,
password ,
password_confirmation: passwordConfirmation
})
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( error . message );
}
const data = await response . json ();
// Store token and authenticate user
localStorage . setItem ( 'auth_token' , data . data . token );
return data . data . user ;
}
// Usage in reset password page
async function handleResetPage () {
const urlParams = new URLSearchParams ( window . location . search );
const token = urlParams . get ( 't' );
try {
// Verify token and show masked identifier
const maskedIdentifier = await verifyResetToken ( token );
document . getElementById ( 'identifier' ). textContent = maskedIdentifier ;
// Handle form submission
document . getElementById ( 'resetForm' ). addEventListener ( 'submit' , async ( e ) => {
e . preventDefault ();
const password = document . getElementById ( 'password' ). value ;
const confirmation = document . getElementById ( 'confirmation' ). value ;
const user = await resetPassword ( token , password , confirmation );
// Redirect to dashboard
window . location . href = '/dashboard' ;
});
} catch ( error ) {
alert ( 'Invalid or expired reset link' );
window . location . href = '/forgot-password' ;
}
}
Usage Notes
The reset token is a combined string of format {token}.{selector} with a total length of 65 characters (40 + 1 + 24)
Tokens typically expire after 1 hour (configurable in backend)
After a successful password reset, the user is automatically authenticated with a new access token
The old password is invalidated immediately upon reset
All existing tokens for the user remain valid unless explicitly revoked
The masked identifier in step 2 helps the user confirm they’re resetting the correct account