Initiate OTP
Generate and send a one-time password to a phone number
Endpoint
POST /api/otp/initiateRequest body
| Field | Type | Required | Description |
|---|---|---|---|
lang | string | Yes | OTP message language: ar or en |
length | integer | Yes | OTP digit count: 4 or 6 |
expiration | integer | Yes | Validity in minutes (1–10) |
sender | string | Yes | Sender ID (max 11 alphanumeric chars) |
payment_type | string | Yes | wallet or subscription |
receiver | string | Yes | Phone number matching a provider pattern configured in the platform |
The OTP code itself is generated by the platform — you cannot supply it, and it is never returned in the response.
Example
curl -X POST https://sms.lamah.com/api/otp/initiate \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"lang": "ar",
"length": 6,
"expiration": 5,
"sender": "MyApp",
"payment_type": "wallet",
"receiver": "00218912345678"
}'Response
{
"request_id": "8c8a6f2f-9a3b-4d86-9b2c-1e3f8f9c2ab1",
"cost": 0.235
}| Field | Description |
|---|---|
request_id | UUID for this OTP request — use this to verify the code the user enters |
cost | The configured single-SMS price, as a decimal amount. An OTP always consumes exactly one SMS |
cost here is a monetary amount, unlike the SMS send endpoints where cost is a count of SMS parts. Do not treat the two interchangeably.
If the platform has no single-SMS price configured, cost falls back to the string "one message" ("رسالة واحدة" under an Arabic locale) rather than a number. This can only surface with payment_type: "subscription" — a wallet request fails earlier with 400 Single SMS cost not found. Parse cost defensively.
Error responses
422 Unprocessable Entity — Validation failed
Covers invalid lang, length, expiration, payment_type, and unrecognised phone numbers.
{
"message": "The selected length is invalid.",
"errors": {
"length": ["The selected length is invalid."]
}
}400 Bad Request — Active OTP already exists
An unverified, unexpired OTP is already outstanding for this number. Wait for it to expire or verify it first.
An OTP that was invalidated by exhausted verification attempts does not block a new request, so a locked-out user can be issued a fresh code straight away.
{ "message": "You have already sent an OTP to this number" }404 Not Found — Sender not found
{ "message": "Sender not found" }400 Bad Request — Provider mismatch
The receiver's network is not one the sender ID is registered on.
Unlike the SMS send endpoints, this endpoint also returns 400 — not 503 — when the operator is merely unreachable for the moment. Retry once after a delay before concluding the pairing is permanently wrong.
{ "message": "The receiver is not from the same provider" }400 Bad Request — Insufficient balance
{ "message": "Insufficient balance" }401 Unauthorized
{ "message": "Invalid or missing token." }429 Too Many Requests
Includes a Retry-After header, in seconds.
{ "message": "Too many OTP requests. Please try again later." }Message content
The SMS body is generated by the platform and cannot be customised. The code is placed first, so it surfaces in the notification preview and in OS autofill.
English (lang: "en")
123456 OTP, valid for 5 minutes. Do not share with anyone.For expiration: 1 the wording becomes 1 minute.
Arabic (lang: "ar")
123456 رمز التحقق، وهو صالح لمدة 5 دقائق. لا تشاركه مع أي أحد.For expiration: 1 the wording becomes 1 دقيقة.
Save the request_id — you'll need it when calling Verify OTP.
Rate limited to 10 OTP requests per hour, counted per phone number per project.