Forgot Password
curl --request POST \
--url https://api.example.com/api/v1/auth/forgot-password \
--header 'Content-Type: application/json' \
--data '
{
"email": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/auth/forgot-password"
payload = { "email": "<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>'})
};
fetch('https://api.example.com/api/v1/auth/forgot-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/forgot-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>'
]),
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/forgot-password"
payload := strings.NewReader("{\n \"email\": \"<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/forgot-password")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/auth/forgot-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}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"email": "<string>"
},
"message": "<string>"
}Authentication
Forgot Password
Request a password reset code via email
POST
/
api
/
v1
/
auth
/
forgot-password
Forgot Password
curl --request POST \
--url https://api.example.com/api/v1/auth/forgot-password \
--header 'Content-Type: application/json' \
--data '
{
"email": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/auth/forgot-password"
payload = { "email": "<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>'})
};
fetch('https://api.example.com/api/v1/auth/forgot-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/forgot-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>'
]),
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/forgot-password"
payload := strings.NewReader("{\n \"email\": \"<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/forgot-password")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/auth/forgot-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}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"email": "<string>"
},
"message": "<string>"
}Endpoint
POST /api/v1/auth/forgot-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. Works for both job seekers and companies.
Request Shape
interface ForgotPasswordDto {
email: string;
}
Response
Success Response (200 OK)
interface ForgotPasswordResponse {
success: boolean;
data: {
email: string;
};
message: string;
meta: {
timestamp: string;
path: string;
method: string;
};
}
{
"success": true,
"data": {
"email": "john.doe@example.com"
},
"message": "If an account exists with that email, you'll receive a password reset link shortly.",
"meta": {
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/forgot-password",
"method": "POST"
}
}
boolean
Always
true for successful requests.object
Contains the submitted email address.
Show Properties
Show Properties
string
The email address that was submitted.
string
Generic success message that doesn’t reveal if the email exists in the system.
Security Behavior
Email Enumeration Prevention: This endpoint always returns the same success response,
regardless of whether the email exists in the database. This prevents attackers from discovering
valid email addresses.
What Actually Happens
1
Immediate Response
API returns success response immediately without waiting for email lookup.
2
Background Processing
System searches for the account in both JobSeeker and Company tables (asynchronously).
3
Email Exists
If account is found: - Generate 6-digit OTP code - Store OTP in Redis with 10-minute expiration -
Queue password reset email for delivery - Email is sent with the reset code
4
Email Doesn't Exist
If account is not found: - Process silently terminates - No email is sent - User sees same
response as successful case
Email Content
If the email exists, the user receives an email containing:- 6-digit OTP code
- Instructions to use the reset password endpoint
- Expiration notice (10 minutes)
- Security warnings
- Information about account protection
OTP Details
OTP Validity: Each password reset code is valid for 10 minutes from generation. After
expiration, users need to request a new code.
OTP Format: The code is a 6-digit numeric string (e.g.,
123456). Only numbers are allowed.Maximum Attempts: Each OTP can be attempted a maximum of 3 times. After 3 failed attempts,
the code is invalidated and a new request is required.
Error Responses
400 Bad Request - Validation Error
Returned when request validation fails.{
"success": false,
"error": {
"message": ["email must be an email"],
"statusCode": 400,
"timestamp": "2026-04-23T20:00:00.000Z",
"path": "/auth/forgot-password",
"method": "POST",
"details": "Bad Request"
}
}
email must be an email- Invalid email formatemail should not be empty- Email field is missing
Validation Rules
| Field | Type | Required | Rules |
|---|---|---|---|
| string | Yes | Must be valid email format |
Rate Limiting Considerations
Best Practice: Implement rate limiting on the client side to prevent abuse. Consider limiting
requests to 3 attempts per email per hour.
Response Time
The response is designed to be consistent regardless of whether the email exists:- ✅ Same response time (~50-100ms)
- ✅ Same HTTP status code (200)
- ✅ Same response structure
- ✅ No indication of email existence
Next Steps
After receiving the password reset code via email:- Use the code with the Reset Password endpoint
- Enter the 6-digit code along with the email and new password
- Wait for confirmation that password has been reset
- Login with the new password using the Login endpoint
Important Notes
Automatic User Type Detection: The system automatically determines whether the account is a
Job Seeker or Company account. You don’t need to specify the user type.
Multiple Requests: If a user requests a password reset multiple times, each new OTP
overwrites the previous one. Only the most recent OTP is valid.
Email Delivery: Email delivery may take a few seconds to a few minutes depending on email
provider. If the user doesn’t receive the email: - Check spam/junk folder - Verify email address
is correct - Request a new code after waiting a few minutes
Security Notes
Privacy First: This endpoint is designed with security and privacy in mind. It never reveals
whether an email address is registered in the system.
Best Practices
- Don’t reveal email existence - Use generic success messages
- Consistent response times - Prevent timing attacks
- Time-limited OTPs - Codes expire after 10 minutes
- Attempt limiting - Maximum 3 verification attempts per OTP
- Single-use codes - Each OTP can only be used once successfully
Related Endpoints
- Reset Password - Complete password reset with OTP code
- Login - Login after resetting password
- Register Job Seeker - Create new job seeker account
- Register Company - Create new company account
