API
SMS API
Send SMS programmatically over HTTP — REST (JSON/form), GET query params, or SOAP/XML — with delivery status, discovery, balance, and the full error-code catalogue.
The public SMS API sends DLT-compliant SMS from any external client over plain HTTP, authenticated with a WRNexus API key.
Base URL & authentication
https://app.wrnexus.com
SMS endpoints live under /sms/* (and are aliased under /api/sms/* for
REST-convention callers — both resolve to the same handlers).
Authenticate every request with your API key in either header:
Authorization: Bearer wrn_live_<your_api_key>
X-API-Key: wrn_live_<your_api_key>
Scopes: sms:send is required to send; sms:read is required for status
and discovery endpoints. A key with no explicit scopes is unrestricted.
The WRNexus SMS model. A message is always sent from a registered sender (
from) using an approved template for that sender’s country onboarding. The template’s{{token}}/{#var#}placeholders are filled from your variables (or, for a single-placeholder template, frommessage). When the sender’s onboarding has exactly one active template,templatemay be omitted.
Send an SMS
The same logical fields can be sent in any of four interchangeable call styles.
| Field | Type | Notes |
|---|---|---|
from |
string | Required. Sender id or sender header. Aliases: sender, senderId, header. |
to |
string | Required. Recipient in E.164 form, e.g. +919812345678. Aliases: recipient, number, msisdn, mobile, phone. |
template |
string | Template id or name. Optional when the sender has a single active template. Aliases: templateId, tpl. |
message |
string | Convenience body that fills a single-placeholder template. Aliases: text, body, msg, content. |
variables |
object | Template token → value map, e.g. { "code": "4821" }. |
flash |
boolean | Send as a flash (class-0) SMS. Default false. |
scheduledAt |
string | RFC3339 timestamp to queue a future send. Aliases: scheduled_at, schedule, at. |
POST /sms/send — REST JSON
curl https://app.wrnexus.com/sms/send \
-H "Authorization: Bearer wrn_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "ACMEIN",
"to": "+919812345678",
"template": "OTP verify",
"variables": { "code": "4821" }
}'
Response 200 OK:
{
"ok": true,
"status": "queued",
"messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
"to": "+919812345678",
"from": "ACMEIN",
"senderId": "snd_01HVZ...",
"templateId": "tpl_01HVZ...",
"renderedBody": "Your code is 4821",
"encoding": "gsm-7",
"segments": 1,
"creditsCost": 1,
"creditsRemaining": 4821,
"flash": false,
"scheduledAt": null,
"createdAt": "2026-06-18T14:30:00.000000"
}
status is queued for an immediate send or scheduled when scheduledAt is
in the future. segments/creditsCost reflect the on-wire SMS segment count
(160 GSM-7 chars per single segment, 70 for UCS-2).
GET /sms/send — query parameters
Every field is a query parameter — the simplest call style:
curl "https://app.wrnexus.com/sms/send?from=ACMEIN&to=%2B919812345678&message=4821" \
-H "Authorization: Bearer wrn_live_xxx"
POST /sms/send — form-encoded
Content-Type: application/x-www-form-urlencoded. Pass each variable as
var.<token>=value (or a variables= JSON string):
curl https://app.wrnexus.com/sms/send \
-H "Authorization: Bearer wrn_live_xxx" \
-d "from=ACMEIN" -d "to=+919812345678" -d "var.code=4821"
POST /sms/soap — SOAP / XML
XML in, XML out, for legacy integrations. Field names mirror the JSON body; the
<variables> children are the tokens.
curl https://app.wrnexus.com/sms/soap \
-H "Authorization: Bearer wrn_live_xxx" \
-H "Content-Type: application/xml" \
--data '<SmsRequest>
<from>ACMEIN</from>
<to>+919812345678</to>
<template>OTP verify</template>
<variables><code>4821</code></variables>
</SmsRequest>'
Response:
<?xml version="1.0" encoding="UTF-8"?>
<SmsResponse>
<ok>true</ok>
<status>queued</status>
<messageId>f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e</messageId>
<to>+919812345678</to>
<from>ACMEIN</from>
<templateId>tpl_01HVZ...</templateId>
<encoding>gsm-7</encoding>
<segments>1</segments>
<creditsCost>1</creditsCost>
<creditsRemaining>4821</creditsRemaining>
<renderedBody>Your code is 4821</renderedBody>
<createdAt>2026-06-18T14:30:00.000000</createdAt>
</SmsResponse>
Errors are returned as the same <SmsResponse> envelope with
<ok>false</ok> and an <error><code/><message/></error> block.
Delivery status
GET /sms/status?id=<messageId>
Look up the delivery status of a message you sent (scope sms:read). The alias
messageId is also accepted.
curl "https://app.wrnexus.com/sms/status?id=f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e" \
-H "Authorization: Bearer wrn_live_xxx"
{
"messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
"status": "delivered",
"to": "+919812345678",
"renderedBody": "Your code is 4821",
"encoding": "gsm-7",
"creditsCost": 1,
"lastError": null,
"scheduledAt": null,
"sentAt": "2026-06-18T14:30:02.000000",
"createdAt": "2026-06-18T14:30:00.000000"
}
An unknown id returns 404 NOT_FOUND.
Discovery & balance
GET /sms/senders
The organization’s registered senders (scope sms:read).
{
"senders": [
{
"senderId": "snd_01HVZ...",
"from": "ACMEIN",
"onboardingId": "onb_01HVZ...",
"dltSenderId": "1107160000000000000",
"isActive": true
}
]
}
GET /sms/templates
The organization’s templates and the variables (placeholder tokens) each one
requires (scope sms:read).
{
"templates": [
{
"templateId": "tpl_01HVZ...",
"name": "OTP verify",
"body": "Your code is {{code}}",
"variables": ["code"],
"onboardingId": "onb_01HVZ...",
"dltTemplateId": "1207160000000000000",
"isActive": true
}
]
}
GET /sms/balance
Remaining SMS credits for the key’s active workspace (scope sms:read).
{
"workspaceId": "ws_01HVZ...",
"creditType": "sms",
"balance": 4821
}
Identity
GET /api/me
Echoes the identity the key resolves to — useful to verify a key and inspect its scopes and expiry. No scope required. See Authentication for the response shape.
Error responses
Errors use a flat, stable contract:
{
"code": "INSUFFICIENT_CREDITS",
"message": "This message costs 1 SMS credit but the workspace only has 0. Top up your SMS credits to send."
}
| Code | HTTP | Description |
|---|---|---|
UNAUTHENTICATED |
401 | Missing, invalid, expired, or revoked API key. |
FORBIDDEN |
403 | Key lacks the required scope or is not bound to an org. |
IP_NOT_ALLOWED |
403 | Caller IP is not on the organization’s allowlist. |
INVALID_SENDER |
400 | from missing, or no such sender for this organization. |
SENDER_INACTIVE |
400 | The resolved sender is not active. |
INVALID_TEMPLATE |
400 | No such template for this organization. |
TEMPLATE_INACTIVE |
400 | The resolved template is not active. |
TEMPLATE_REQUIRED |
400 | Sender has no single active template — pass template. |
TEMPLATE_AMBIGUOUS |
400 | Sender has several active templates — pass template. |
SENDER_TEMPLATE_MISMATCH |
400 | Sender and template belong to different onboardings. |
ONBOARDING_NOT_APPROVED |
400 | The sender’s country onboarding is not approved yet. |
DLT_SENDER_REQUIRED |
400 | This country needs a DLT sender id on the sender. |
DLT_TEMPLATE_REQUIRED |
400 | This country needs a DLT template id on the template. |
INVALID_RECIPIENT |
400 | to missing or not a valid number for the country. |
MISSING_VARIABLES |
400 | One or more template placeholders have no value. |
INVALID_VARIABLES |
400 | variables is not a JSON object of token → value. |
INVALID_SCHEDULED_AT |
400 | scheduledAt is not an RFC3339 timestamp. |
SCHEDULED_AT_IN_PAST |
400 | scheduledAt is not in the future. |
INSUFFICIENT_CREDITS |
402 | The workspace’s SMS balance is below the message cost. |
NO_WORKSPACE |
409 | The organization has no active workspace to send from. |
NOT_FOUND |
404 | Message (status lookup) not found. |
INTERNAL_ERROR |
500 | An unexpected server error. |
See also
- SMS SDK — typed clients that wrap these endpoints.
- SMS Webhooks — receive
sms.delivered/sms.failedevents. - Authentication — create and secure an API key.