Skip to content
Documentation menu

Documentation

POST /otp/send

Start a verification. Generate a one-time code and deliver it to the recipient over the channel your account routing chooses, or return a WhatsApp link the user opens to receive the code.

Start a verification: generate a code and deliver it to the recipient. The channel is chosen by your app’s routing (default order plus per-country overrides), so you do not pass one. An optional locale picks the message language.

Pass client_ip whenever you can: it is the IP of the end user who triggered the OTP (from your request context, e.g. req.ip behind your load balancer), not your server’s address. Requests without it share a much tighter per-app rate limit, and the value powers IP-based abuse protection for your own traffic: VPN/proxy/Tor and datacenter IPs are throttled, and known-abusive IPs are refused outright. A private or reserved address counts as absent.

When routing selects WhatsApp, the code is not sent yet: the response carries an action_url (a wa.me link) the user opens to receive the code over chat, and the OTP stays pending until they enter it and you verify. See WhatsApp verification.

curl -X POST https://api.otp.com/api/v1/otp/send \
  -H "Authorization: Bearer otp_live_•••" \
  -H "Content-Type: application/json" \
  -d '{"recipient":"+14155552671","locale":"en","client_ip":"81.2.69.142"}'

Request body

Field Type Required Description
recipient string yes Phone number in E.164 form (+14155552671) or an email address.
locale string no Message language, for example en. Defaults to your app setting.
client_ip string no IP of the end user who triggered the OTP (IPv4 or IPv6). Strongly recommended: requests without it share a much tighter rate limit. A malformed value is rejected (422); a private/reserved one counts as absent.

There is no channel field. Routing selects the channel from the recipient and your configuration. See Channels & routing.

Idempotency

Send an idempotency-key header and a repeat of the same call replays the first response instead of sending a second code. Use it wherever a retry is possible: a network timeout, a queue redelivery, a user double-tap on your own submit button. Scope the key to the thing being verified, for example signup:<user_id>.

curl -X POST https://api.otp.com/api/v1/otp/send \
  -H "Authorization: Bearer otp_live_•••" \
  -H "Content-Type: application/json" \
  -H "idempotency-key: signup:8f21c4" \
  -d '{"recipient":"+14155552671","client_ip":"81.2.69.142"}'

The key is scoped to your company and the replay is matched on the key alone, so give each verification its own key: reuse it for a different recipient and you get the first recipient’s OTP back, not a new one. Keys are at most 128 characters; a longer one is a 422. The SDKs expose the key as a parameter on the send call.

Response

{
  "otp_id": "9f3c1b2a-…",
  "status": "pending",
  "channel": "sms",
  "masked_recipient": "+1****71",
  "action_url": null
}
Field Description
otp_id Identifier for this verification. Pass it to verify, resend, and status.
status Always pending on a fresh send.
channel The channel the OTP was routed to.
masked_recipient The recipient with most characters masked, safe to log or display.
action_url A WhatsApp wa.me link the user opens to receive the code over chat, when the channel is whatsapp; null on every other channel.

The code itself is never returned. On WhatsApp it is not sent with the response: the user opens the action_url to receive the code over chat, then enters it and you verify it like any other channel.

Common errors

Status Type When
422 InvalidRecipientError recipient is not a valid phone or email.
403 GeoBlockedError The recipient’s country is blocked.
402 InsufficientFundsError Not enough balance to send.
403 IpReputationBlockedError The declared client_ip is a known abuse source.
429 RateLimitExceededError Too many requests for this recipient, or too many without a usable client_ip (the message names the scope).
422 InvalidIdempotencyKeyError The idempotency-key header is longer than 128 characters.

See Error codes for the full list.