Reset Password
curl --request POST \
--url https://api.example.com/api/v1/auth/reset-password \
--header 'Content-Type: application/json' \
--data '
{
"email": "<string>",
"code": "<string>",
"newPassword": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/auth/reset-password"
payload = {
"email": "<string>",
"code": "<string>",
"newPassword": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: '<string>', code: '<string>', newPassword: '<string>'})
};
fetch('https://api.example.com/api/v1/auth/reset-password', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/auth/reset-password",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'email' => '<string>',
'code' => '<string>',
'newPassword' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/auth/reset-password"
payload := strings.NewReader("{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/auth/reset-password")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/auth/reset-password")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"message": "<string>"
},
"message": "<string>"
}Authentication
Reset Password
Reset user password using OTP code
POST
/
api
/
v1
/
auth
/
reset-password
Reset Password
curl --request POST \
--url https://api.example.com/api/v1/auth/reset-password \
--header 'Content-Type: application/json' \
--data '
{
"email": "<string>",
"code": "<string>",
"newPassword": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/auth/reset-password"
payload = {
"email": "<string>",
"code": "<string>",
"newPassword": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: '<string>', code: '<string>', newPassword: '<string>'})
};
fetch('https://api.example.com/api/v1/auth/reset-password', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/auth/reset-password",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'email' => '<string>',
'code' => '<string>',
'newPassword' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/auth/reset-password"
payload := strings.NewReader("{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/auth/reset-password")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/auth/reset-password")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"<string>\",\n \"code\": \"<string>\",\n \"newPassword\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"message": "<string>"
},
"message": "<string>"
}Endpoint
POST /api/v1/auth/reset-password
http://localhost:3000/api/v1
This endpoint is public and does not require authentication.
Request Body
string
required
The email address associated with the account. Must match the email used in the forgot password
request.
string
required
The 6-digit OTP code sent to the email. Must be exactly 6 numeric digits.
string
required
The new password for the account. Minimum 6 characters.
Request Shape
interface ResetPasswordDto {
email: string;
code: string;
newPassword: string;
}
Response
Success Response (200 OK)
interface ResetPasswordResponse {
success: boolean;
data: {
message: string;
};
message: string;
meta: {
timestamp: string;
path: string;
method: string;
};
}
{
"success": true,
"data": {
"message": "Password has been reset successfully"
},
"message": "Success",
"meta": {
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/reset-password",
"method": "POST"
}
}
boolean
Indicates if the password reset was successful.
string
Human-readable success message: “Password has been reset successfully”
What Happens on Password Reset
1
OTP Verification
System verifies the OTP code against the stored value in Redis.
2
Expiration Check
Ensures the OTP hasn’t expired (10-minute validity window).
3
Attempt Counter Check
Verifies that maximum attempts (3) haven’t been exceeded.
4
User Type Detection
System determines if the user is a Job Seeker or Company based on stored OTP data.
5
Password Hashing
New password is securely hashed using bcrypt algorithm.
6
Database Update
User’s password is updated in the database (JobSeeker or Company table).
7
Session Invalidation
All existing refresh tokens are invalidated, logging out all active sessions.
8
OTP Deletion
Used OTP is immediately deleted from Redis.
9
Success Response
Confirmation message is returned to the user.
Important: Session Invalidation
All Sessions Logged Out: After a successful password reset, all active sessions are
automatically terminated. The user must login again with the new password.
- ✅ Any potentially compromised sessions are invalidated
- ✅ Only the user with the new password can access the account
- ✅ All devices are logged out for security
Error Responses
400 Bad Request - Validation Error
Returned when request validation fails.{
"success": false,
"error": {
"message": [
"email must be an email",
"code must be exactly 6 characters long",
"newPassword must be longer than or equal to 6 characters"
],
"statusCode": 400,
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/reset-password",
"method": "POST",
"details": "Bad Request"
}
}
email must be an email- Invalid email formatcode must be exactly 6 characters long- Code is not 6 digitsnewPassword must be longer than or equal to 6 characters- Password too short
401 Unauthorized - Invalid or Expired Code
Returned when OTP is incorrect, expired, or doesn’t exist.{
"success": false,
"error": {
"message": "expired reset code",
"statusCode": 401,
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/reset-password",
"method": "POST",
"details": "Unauthorized"
}
}
- OTP code is incorrect
- OTP has expired (10 minutes validity)
- OTP was already used
- Email doesn’t match the email used in forgot password request
- OTP was never generated for this email
- Maximum verification attempts (3) exceeded
401 Unauthorized - User Not Found
Returned when no user exists with the provided email.{
"success": false,
"error": {
"message": "User not found",
"statusCode": 401,
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/reset-password",
"method": "POST",
"details": "Unauthorized"
}
}
500 Internal Server Error
Returned when an unexpected error occurs during password reset.{
"success": false,
"error": {
"message": "Password reset failed",
"statusCode": 500,
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/reset-password",
"method": "POST",
"details": "Internal Server Error"
}
}
Validation Rules
| Field | Type | Required | Rules |
|---|---|---|---|
| string | Yes | Must be valid email format | |
| code | string | Yes | Must be exactly 6 digits, numeric only (0-9) |
| newPassword | string | Yes | Minimum 6 characters (8+ recommended) |
Password Requirements
Minimum Length: 6 characters
Recommended: Use at least 8 characters with a mix of uppercase, lowercase, numbers, and
special characters for better security.
Strong Password Guidelines
A strong password should include:- ✅ At least 8 characters (minimum is 6)
- ✅ Mix of uppercase and lowercase letters
- ✅ At least one number
- ✅ At least one special character (@, #, $, %, etc.)
- ❌ Avoid common words or patterns
- ❌ Don’t reuse old passwords
OTP Behavior
OTP Validity: Each password reset code is valid for 10 minutes from generation. After
expiration, users need to request a new code via Forgot
Password.
Maximum Attempts: Each OTP can be verified a maximum of 3 times. After 3 failed attempts,
the code is invalidated and a new request is required.
Single Use: Once the password is successfully reset, the OTP is immediately deleted and cannot
be reused.
After Password Reset
Once password is successfully reset:- ✅ All active sessions are logged out
- ✅ All refresh tokens are invalidated
- ✅ Password is securely updated in the database
- ✅ OTP code is deleted from Redis
- ✅ User must login again with the new password
Next Steps
After successful password reset:- Login using the Login endpoint with the new password
- Save credentials securely in your application
- Inform user that all other sessions have been logged out
Security Notes
Automatic User Type Detection: The system automatically determines whether the account is a
Job Seeker or Company account based on the OTP data. You don’t need to specify the user type.
Case Sensitive Password: Unlike email, the password is case-sensitive. Make sure users enter
it exactly as intended.
Security Features
- Bcrypt Hashing - Passwords are hashed using bcrypt with salt rounds
- Session Invalidation - All existing sessions are terminated
- Single-Use OTP - Each code can only be used once successfully
- Time-Limited OTP - Codes expire after 10 minutes
- Attempt Limiting - Maximum 3 verification attempts per OTP
- Secure Storage - Passwords are never stored in plain text
Common Issues & Solutions
| Issue | Solution |
|---|---|
| ”Invalid or expired reset code” | Request a new code via forgot password endpoint |
| ”User not found” | Verify email address is correct |
| OTP expired | Codes are valid for 10 minutes - request a new one |
| Too many attempts | Request a new code after 3 failed attempts |
| Email doesn’t match | Use the same email from forgot password request |
Related Endpoints
- Forgot Password - Request password reset code
- Login - Login with new password after reset
- Register Job Seeker - Create new job seeker account
- Register Company - Create new company account
