Developer hub
Ship SMS before your coffee cools
Five REST endpoints, one auth header, one error shape. Send messages and one-time passwords to Nepal from any language that can make an HTTPS request.
Base URL
https://api.smsray.in/api/sms/v1
Auth
x-api-key: <your key>
Errors
{ "error", "code", "details"? }
Quickstart
Three steps to your first message
- Step 1
Create a workspace
Sign in at app.smsray.in with your phone number or email. New workspaces start as pending until our team activates them.
- Step 2
Create an API key in Clients
Open Clients, add one per app or environment, and copy the key and webhook secret — they're shown once and stored only as hashes.
- Step 3
Send
POST to
/sms/sendwith thex-api-keyheader. You get amessageIdback instantly and the status follows.
Send an SMS
One POST. The reply carries the messageId, segment count and encoding.
curl https://api.smsray.in/api/sms/v1/sms/send \
-H "x-api-key: $SMSRAY_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{ "to": "9779801234567", "text": "Your order #1042 has shipped." }'Verify a phone number
We generate the code, send it and check it. Valid 5 minutes, 5 attempts.
# 1. Send a code (6 digits, valid 5 minutes)
curl https://api.smsray.in/api/sms/v1/sms/otp/send \
-H "x-api-key: $SMSRAY_API_KEY" -H "content-type: application/json" \
-d '{ "to": "9779801234567", "purpose": "login", "ip": "203.0.113.7" }'
# 2. Check what the user typed
curl https://api.smsray.in/api/sms/v1/sms/otp/verify \
-H "x-api-key: $SMSRAY_API_KEY" -H "content-type: application/json" \
-d '{ "to": "9779801234567", "purpose": "login", "code": "482913" }'| Method | Path | What it does |
|---|---|---|
| POST | /sms/send | Send a transactional, OTP or promotional message |
| POST | /sms/otp/send | Generate and send a 6-digit code |
| POST | /sms/otp/verify | Check the code your user typed |
| GET | /sms/messages/:id | Look up a message and its status |
| GET | /sms/balance | Workspace balance, rates and your counters |
API reference
Everything you need, nothing you don't
Short pages, real examples, every field and error code.
Quickstart
Your first SMS, step by step
Authentication
The x-api-key header and key hygiene
Send SMS
POST /sms/send — fields and responses
OTP
Send and verify six-digit codes
Messages
GET /sms/messages/:id and statuses
Balance
GET /sms/balance, rates and counters
Webhooks
Signed message.status and message.inbound
Errors
One error shape, every code explained
Rate limits
Per-client send rate (20/s default) and OTP limits
Idempotency
Safe retries with Idempotency-Key (24 h)
Encoding & segments
GSM-7, Unicode and how segments are counted
Webhooks
Verify every event in a few lines
Each delivery carries x-lacspace-signature: t=…,v1=… — an HMAC-SHA256 of the timestamp and raw body with your webhook secret.
- Events:
message.statusandmessage.inbound - Reject timestamps outside ±300 s to stop replays
- During secret rotation two
v1values are sent — accept either - 8 attempts with backoff: 10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h
- Respond 2xx within 10 s; use
x-lacspace-deliveryto de-duplicate
import crypto from "node:crypto";
// x-lacspace-signature: t=<unix>,v1=<hex>[,v1=<hex during secret rotation>]
export function verifySmsrayWebhook(rawBody, header, secret, toleranceSec = 300) {
const pairs = header.split(",").map((p) => p.split("="));
const t = Number(pairs.find(([k]) => k === "t")?.[1]);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = Buffer.from(
crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"), "hex");
return pairs
.filter(([k]) => k === "v1")
.some(([, v]) => {
const sig = Buffer.from(v, "hex");
return sig.length === expected.length && crypto.timingSafeEqual(sig, expected);
});
}Testing tips
Build it right the first time
Test with your own number
Send to your own Nepal mobile number (96/97/98XXXXXXXX) first and watch the message move in the dashboard. Every send, including tests, is billed from your balance.
Always send an Idempotency-Key
Use something stable like order-1042-shipped. If your job retries, the replay returns the original response with Idempotent-Replayed: true — no second SMS, no second charge.
Poll status while you build
Before your webhook endpoint is live, call GET /sms/messages/:id to follow a message through queued, sent and delivered. Switch to webhooks in production.
Treat a wrong OTP as data
A wrong code returns 200 with verified: false and a reason. Pass the end user's ip on send to turn on per-IP limits.
SDKs
REST-first. Official libraries coming soon.
Today SMSRay is a plain JSON-over-HTTPS API, so it works from any stack with no package to install. The docs show cURL, Node.js, Python and PHP for every endpoint. Official client libraries are on the way.
Get your API key
Create a workspace with your phone number or email, add a client and send your first message from the terminal.