sms
SMS
A text message. Recipient is E.164
(+15551234567).
Price can vary by country. The approved Sender ID is the name in the message.
You send a 6-digit code to your user. They type it. You never see the digits — you hold a reference. Authentication and verification only. Not a broadcast channel.
An account, a Bearer key, a funded USD balance, and an approved Sender ID. Sends return
403 until that name is approved.
Impersonating a bank will not work. We checked.
Programming bar: HTTP + JSON. Persist the
reference. Never log the token.
You pick a channel per send. The code, the expiry, and the verify step stay the same. What changes is the inbox the user opens — and the recipient format you send us.
sms
A text message. Recipient is E.164
(+15551234567).
Price can vary by country. The approved Sender ID is the name in the message.
Same 6 digits, same verify, different inbox. Recipient is still E.164. If they live in WhatsApp, send them there.
Same code, to an inbox. Recipient is an email address. One price, everywhere — email is not billed per country.
Who actually delivers the message is an internal routing decision. You send
sms,
whatsapp, or
email. That is the contract.
You pay when the send is accepted, in USD. Guessing the code is free. If the wallet cannot cover it, the
API returns 400.
Rates will appear here once they are published. Authenticated
GET /otp/pricing
is the same card.
Country-specific rows win when they exist. Email has one rate.
Your HTTP client will retry. Timeout, 502, a cat on the enter key. Send
idempotency_key
as shs- plus a UUID. Omit it and we mint one
and hand it back.
reference,
status 200, and
Idempotency-Replayed: true.
No second text. No second charge. That is the point of the header.
superseded
once the new one actually goes out.
Reuse the key on retry. A replay returns 200 and Idempotency-Replayed: true.
https://seamtel.com/api/v1/otp/send
{
"channel": "sms",
"recipient": "+15551234567",
"idempotency_key": "shs-550e8400-e29b-41d4-a716-446655440000"
}
You get a reference and an
expires_at. You do not get the digits.
Store the reference. The user reads the code on their phone or in their inbox.
| Field | Notes |
|---|---|
| channel | sms, whatsapp, or email |
| recipient | E.164 for SMS and WhatsApp. An email address for email. |
| idempotency_key | Optional. shs- + UUID. We mint one if you skip it. |
You hold a reference. They hold the digits.
https://seamtel.com/api/v1/otp/send
{
"channel": "sms",
"recipient": "+15551234567"
}
Pair the reference with the token they typed. Success is
{ verified: true }.
Failure is 400 with a stable
errors.code.
“It didn’t work” is not an error model.
| errors.code | Meaning |
|---|---|
| invalid_token | Wrong code |
| expired | The window passed |
| superseded | A newer code was sent |
| pending | Not ready yet — retry shortly |
| already_verified | Used once. Done. |
| too_many_attempts | Request a new code |
Reference plus the token they typed.
https://seamtel.com/api/v1/otp/verify
{
"reference": "550e8400-e29b-41d4-a716-446655440000",
"token": "123456"
}
Same envelope as the rest of the API:
{ msg, data }
on success. Every request:
Authorization: Bearer SHS_your_api_key.
| Group | Method | Path | Purpose |
|---|---|---|---|
| Account | GET | /me | Balance before a batch |
| Rate card | GET | /otp/pricing | Prices by channel |
| Send | POST | /otp/send | Save the reference |
| Verify | POST | /otp/verify | Pair reference + token |
Read the field-by-field schema
Or try a request in the API Reference.