إرسال
OTP

Verify OTP

Verify the OTP code entered by the user

Endpoint

POST /api/otp/verify

Request body

FieldTypeRequiredDescription
request_idstring (UUID)YesThe request_id from Initiate OTP
codestringYesThe OTP code entered by the user (4 or 6 digits)

Example

curl -X POST https://sms.lamah.com/api/otp/verify \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "request_id": "8c8a6f2f-9a3b-4d86-9b2c-1e3f8f9c2ab1",
    "code": "123456"
  }'

Response

{
  "message": "OTP verified successfully"
}
FieldDescription
messageSuccess confirmation message

Error responses

401 Unauthorized — Invalid code

Returned while the request still has verification attempts left.

{ "message": "Invalid OTP" }

429 Too Many Requests — Attempts exhausted

All 3 attempts have been used and the OTP has been invalidated. It cannot be verified again even with the correct code — call Initiate OTP for a fresh one.

The third failed attempt returns 429 rather than 401, so you learn on the failing call instead of the next one.

{ "message": "Too many invalid OTP attempts. Request a new OTP." }

401 Unauthorized — Expired OTP

{ "message": "OTP has expired" }

400 Bad Request — Already verified

{ "message": "OTP already verified" }

404 Not Found — Invalid request ID

Returned both when the request_id is unknown and when it belongs to another project.

{ "message": "OTP not found" }

422 Unprocessable Entity — Validation failed

{
  "message": "The request id field must be a valid UUID.",
  "errors": {
    "request_id": ["The request id field must be a valid UUID."]
  }
}

401 Unauthorized — Bad token

{ "message": "Invalid or missing token." }

A wrong code, an expired code, and a bad API token all return 401. Distinguish them by the message field — do not treat every 401 from this endpoint as an authentication failure.

OTP flow

User enters phone → POST /otp/initiate → save request_id
User receives SMS → enters code → POST /otp/verify → success/fail

Verification rules

  • 3 attempts: each request_id allows exactly three wrong guesses. The limit is fixed and not configurable. Spending it invalidates the OTP permanently
  • Single use: once successfully verified, the OTP cannot be used again — reverifying returns 400
  • Expiration: the OTP expires after the duration set in expiration during initiation
  • One active OTP per number: while an unverified, unexpired, non-invalidated OTP exists, Initiate OTP rejects new requests for that number. An OTP that was invalidated by exhausted attempts no longer blocks a new one, so a locked-out user can request a fresh code immediately rather than waiting out the expiry

The remaining attempt count is not returned. If you want to show it, track failures against the request_id on your side.

An expired OTP reports 401 OTP has expired even if its attempts were already exhausted — expiry is checked first. Do not rely on 429 alone to detect lockout near the expiry boundary.

Attempt limiting caps guesses per OTP, not per phone number. Since a locked-out request can immediately be replaced, the practical ceiling is about 30 guesses per hour per number (3 attempts × the 10/hour cap on Initiate OTP), and each replacement costs an SMS. Prefer length: 6 over length: 4 for anything sensitive, and keep expiration short.

On this page