إرسال
OTP

Initiate OTP

Generate and send a one-time password to a phone number

Endpoint

POST /api/otp/initiate

Request body

FieldTypeRequiredDescription
langstringYesOTP message language: ar or en
lengthintegerYesOTP digit count: 4 or 6
expirationintegerYesValidity in minutes (1–10)
senderstringYesSender ID (max 11 alphanumeric chars)
payment_typestringYeswallet or subscription
receiverstringYesPhone 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
}
FieldDescription
request_idUUID for this OTP request — use this to verify the code the user enters
costThe 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.

On this page