Auth API
Verify OTP — API Reference
Complete API reference for the Verify OTP endpoint. Verification flow, security considerations, and error handling.
Endpoint
POST /v1/auth/verify-otp
Request Body
{
"request_id": "auth_abc123def456",
"otp": "123456"
}
Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| request_id | string | ✅ | The request ID from the send-otp response |
| otp | string | ✅ | The OTP code entered by the user |
Response
Verified (200)
{
"success": true,
"verified": true,
"phone": "+919876543210",
"channel": "sms",
"metadata": {
"user_id": "usr_123",
"action": "login"
},
"verified_at": "2026-01-15T10:32:00Z"
}
Invalid OTP (200)
{
"success": true,
"verified": false,
"attempts_remaining": 2,
"message": "Invalid OTP. 2 attempts remaining."
}
Expired (200)
{
"success": true,
"verified": false,
"attempts_remaining": 0,
"message": "OTP has expired. Please request a new one."
}
Error Codes
| Code | HTTP | Description |
|------|------|-------------|
| INVALID_REQUEST_ID | 400 | Request ID not found or already consumed |
| OTP_EXPIRED | 400 | OTP has expired |
| MAX_ATTEMPTS_EXCEEDED | 400 | Maximum verification attempts (3) exceeded |
| ALREADY_VERIFIED | 400 | OTP already successfully verified |
Security Features
Attempt Limiting
Each OTP allows 3 verification attempts. After 3 failed attempts, the OTP is invalidated and the user must request a new one.
One-Time Use
Once an OTP is successfully verified, it cannot be used again. The request_id is consumed.
Expiry
OTPs automatically expire after the configured expiry_seconds (default: 5 minutes). Expired OTPs cannot be verified.
Rate Limiting
Verification attempts are rate-limited per phone number to prevent brute-force attacks:
| Limit | Value | |-------|-------| | Failed verifications per number | 10 / hour | | Cooldown after max failures | 1 hour lockout |
Complete Flow Example
const express = require('express');
const StartMessaging = require('@startmessaging/node');
const app = express();
const client = new StartMessaging(process.env.SM_API_KEY);
// Step 1: User requests OTP
app.post('/auth/request-otp', async (req, res) => {
const { phone } = req.body;
const result = await client.auth.sendOTP({
phone,
channel: 'auto',
otp_length: 6,
expiry_seconds: 300,
});
// Store request_id in session
req.session.otpRequestId = result.request_id;
res.json({ success: true, message: 'OTP sent' });
});
// Step 2: User submits OTP
app.post('/auth/verify-otp', async (req, res) => {
const { otp } = req.body;
const requestId = req.session.otpRequestId;
const result = await client.auth.verifyOTP({
request_id: requestId,
otp,
});
if (result.verified) {
// OTP verified — create session / JWT
req.session.authenticated = true;
req.session.phone = result.phone;
delete req.session.otpRequestId;
res.json({ success: true, verified: true });
} else {
res.json({
success: true,
verified: false,
attemptsRemaining: result.attempts_remaining,
});
}
});
FAQ
What happens if the user doesn’t receive the OTP? Call the send-otp endpoint again with the same phone number. A new OTP will be generated and sent. The old OTP is automatically invalidated.
Can I verify an OTP multiple times? No — each OTP is single-use. Once verified, the request_id is consumed. If you need to re-verify, send a new OTP.
How secure is the OTP? OTPs are generated using a cryptographically secure random number generator. They are stored as salted hashes on our servers — we never store plain-text OTPs.