API Documentation
Complete guide to integrate with our SMS API. Use your API token to authenticate all requests.
Quick Start
1. Generate an API Key
Go to API Integration → API Token in the sidebar and click Add Token. Copy the key immediately — it will only be shown once.
2. Add the Key to Your Request
Include your API key using the X-API-Key header (recommended):
X-API-Key: nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx3. Send Your First SMS
Use the POST /api/v1/sms/send endpoint:
curl -X POST "https://your-domain.com/api/v1/sms/send" \
-H "X-API-Key: nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "9840123456",
"message": "Hello from NestSMS!",
"sender_id": "NestSMS"
}'4. Save the job_id
The response includes a job_id. Use it with GET /api/v1/sms/status/:messageId to track delivery status.
Authentication
Method 1: X-API-Key Header Recommended
Send your API key in the X-API-Key header. This is the preferred method for all API requests.
const response = await fetch('https://your-domain.com/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '9840123456',
message: 'Hello from NestSMS!',
sender_id: 'NestSMS'
})
});
const result = await response.json();Method 2: Authorization Bearer Token
Alternatively, pass your API key as a Bearer token in the Authorization header.
const response = await fetch('https://your-domain.com/api/v1/sms/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '9840123456',
message: 'Hello from NestSMS!'
})
});Python Example
import os
import requests
API_KEY = os.environ.get('NESTSMS_API_KEY') # never hardcode
BASE_URL = 'https://your-domain.com/api/v1/sms'
HEADERS = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
}
response = requests.post(
f'{BASE_URL}/send',
headers=HEADERS,
json={
'to': '9840123456',
'message': 'Hello from NestSMS!',
'sender_id': 'NestSMS'
}
)
result = response.json()Key Prefixes
nsms_live_ — Live key for production. Charges real balance.
⚠️ Security Warning
Never hardcode API keys in source code. Use environment variables. Never expose keys in client-side JavaScript or mobile apps.
SMS Endpoints
/api/v1/sms/sendSend a single SMS or bulk SMS to multiple recipients.
Parameters
Recipient number(s) — single number, array, or comma-separated string (e.g. '9840123456' or ['9840123456', '9851234567'])
Message content (max 720 chars for text, 335 for unicode)
Sender ID to use. Defaults to your account's default sender ID
Encoding: 'text', 'unicode', or 'flash'. Default: 'text'
Purpose: 'promotional', 'transactional', or 'otp'. Default: 'transactional'
Example Request
// Single SMS
const sendSingleSMS = async () => {
const response = await fetch('https://your-domain.com/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '9840123456',
message: 'Hello from NestSMS!',
sender_id: 'NestSMS',
type: 'text',
message_type: 'transactional'
})
});
return await response.json();
};
// Bulk SMS (array)
const sendBulkSMS = async () => {
const response = await fetch('https://your-domain.com/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: ['9840123456', '9851234567', '9860123456'],
message: 'Bulk message to all recipients!',
sender_id: 'NestSMS',
message_type: 'promotional'
})
});
return await response.json();
};Example Response
// Single SMS response
{
"success": true,
"message": "SMS queued successfully",
"data": {
"job_id": "job-uuid-here",
"cost": 1.0,
"remaining_balance": 499.0
}
}
// Bulk SMS response
{
"success": true,
"message": "Bulk SMS queued successfully",
"data": {
"queued_count": 3,
"total_cost": 3.0,
"remaining_balance": 497.0,
"job_ids": ["job-uuid-1", "job-uuid-2", "job-uuid-3"]
}
}/api/v1/sms/balanceGet your current wallet balance and usage statistics.
Example Request
const checkBalance = async () => {
const response = await fetch('https://your-domain.com/api/v1/sms/balance', {
method: 'GET',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
});
return await response.json();
};Example Response
{
"success": true,
"data": {
"balance": 500.0,
"currency": "NPR",
"per_message_cost": 1.0,
"messages_remaining": 500,
"usage": {
"total_messages_sent": 1234,
"successful": 1200,
"delivered": 1150,
"failed": 34,
"total_spent": 1234.0
}
}
}/api/v1/sms/status/:messageIdCheck the delivery status of a specific message by its ID.
Parameters
Message ID returned from the send response
Example Request
const getMessageStatus = async (messageId) => {
const response = await fetch(
'https://your-domain.com/api/v1/sms/status/' + messageId,
{
method: 'GET',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
}
);
return await response.json();
};Example Response
{
"success": true,
"data": {
"message_id": "msg-uuid-here",
"status": "delivered",
"recipient": "9840123456",
"sender_id": "NestSMS",
"message_type": "text",
"sent_at": "2025-11-27T08:30:00.000Z",
"delivered_at": "2025-11-27T08:30:15.000Z",
"failed_at": null,
"error_code": null,
"error_message": null,
"cost": 1.0,
"delivery_reports": [
{
"status": "delivered",
"received_at": "2025-11-27T08:30:15.000Z",
"network": "NTC"
}
]
}
}Template Usage
Create and use reusable message templates with dynamic variables. Templates let you define a message once and reuse it with placeholders.
Creating a Template
Go to Settings → Templates → New Template and define your message with placeholders:
Hello {{name}}, your OTP is {{otp}}. Valid for {{expiry}} minutes.Using a Template in the API
Pass the template_id and a variables map instead of a raw message:
const response = await fetch('https://your-domain.com/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': 'nsms_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '9840123456',
template_id: 'tpl_otp_123',
variables: {
name: 'Aarav',
otp: '847291',
expiry: '5'
}
})
});Error Codes Reference
All error responses follow this shape:{ "success": false, "error": "Human-readable message", "code": "ERROR_CODE" }
| Code | HTTP | Description | Fix |
|---|---|---|---|
NO_API_KEY | 401 | No API key in request | Add X-API-Key header |
INVALID_API_KEY | 401 | Key not found or malformed | Check your key value |
API_KEY_EXPIRED | 401 | Key has passed its expiry date | Generate a new key |
ORG_INACTIVE | 403 | Organization is deactivated | Contact support |
IP_RESTRICTED | 403 | Request from a non-whitelisted IP | Add your IP to the key's whitelist |
INSUFFICIENT_PERMISSIONS | 403 | Key missing required permission | Add the permission when creating the key |
MISSING_TO | 400 | 'to' field not provided | Include recipient number(s) |
MISSING_MESSAGE | 400 | 'message' field not provided | Include message content |
INVALID_TO_FORMAT | 400 | Invalid phone number format | Use numeric format e.g. 9840123456 |
TOO_MANY_RECIPIENTS | 400 | More than 1000 recipients in one request | Split into batches of ≤1000 |
MESSAGE_TOO_LONG | 400 | Message exceeds max length | Max 720 chars (text), 335 (unicode) |
NO_SENDER_ID | 400 | No sender ID configured | Set up a sender ID in Settings |
INVALID_SENDER_ID | 400 | Sender ID not found or not approved | Use an approved sender ID |
INSUFFICIENT_BALANCE | 400 | Wallet balance too low | Top up your wallet |
MESSAGE_NOT_FOUND | 404 | messageId not found | Check the ID from the send response |
ACCESS_DENIED | 403 | Resource belongs to another organization | Verify you're using the correct key |
RATE_LIMIT_EXCEEDED | 429 | Too many requests in the time window | Wait retry_after seconds before retrying |
Rate Limits
Default limits apply per API key. Higher limits are available on request.
| Window | Default Limit |
|---|---|
| Per minute | 60 requests |
| Per hour | 1,000 requests |
Rate limited response (429)
{
"success": false,
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"retry_after": 30
}Use the retry_after value (in seconds) before retrying.
Need Help?
If you have questions or encounter issues, refer to the documentation or contact our support team.
Documentation
Full API reference and integration guides
Support
Contact us for technical assistance
White-Label Setup Guide
This guide walks you through all required configuration steps to deploy your fully branded SMS service to your clients.
Getting Started
Welcome to the NestSMS White-Label platform. Complete each step in order — skipping steps may cause configuration errors that are difficult to diagnose.
- Make sure you have reseller account on the platform.
- Access to your domain's DNS panel (Cloudflare, GoDaddy, Namecheap, etc.)
- A reseller account on the platform. Your brand's primary color hex code, logo file (SVG or PNG), and favicon (32×32 px)
Navigate to Settings
From your Dashboard follow the below instructions.
Steps to navigate to settings
- 1. Make sure you are in dashboard.
- 2. In your left sidebar, click on the Settings icon (gear symbol) to open the settings menu.
- 3. From the settings menu, you can access to your organization settings.
Set Primary Color
On the Organizations settings page, scroll down to the Branding section.
Setting the primary color
- 1. Locate the Primary Color field
- 2. Enter your brand's hex color code (e.g. #1A73E8) or use the color picker
- 3. The color will be applied across your white-labeled portal — buttons, navbar, and accents
Create CNAME Records at Your DNS Provider
Log in to your domain DNS provider and create two CNAME records pointing to our platform:
- 1. Locate the Primary Color field.
- 2. Enter your brand's hex color code (e.g. #1A73E8) or use the color picker.
- 3. The color will be applied across your white-labeled portal — buttons, navbar, and accents.
| Type | Name (Host) | Value |
|---|---|---|
| TXT | _nestsms-verify.yourdomain.com | nestsms-verify=abc123xyzxxxxxxxxxxxxxxxx |
Enter Frontend CNAME in Settings
Back in Settings → Organizations, scroll to the Domain Configuration section.
- 1.In the Frontend URL field, enter the full URL of your frontend CNAME:
- 1.In Replace app.yourdomain.com with your actual subdomain and domain
https://app.yourdomain.comEnter Backend CNAME in Settings
In the same Domain Configuration section:
- 1.In the Backend URL field, enter the full URL of your API CNAME:
- 1.Click Save to generate your verification token
https://app.yourdomain.comInsert TXT Record for Domain Verification
After saving, the platform generates a unique TXT verification token for your domain.
- 1.Copy the generated token shown on the settings page — it will look like:
- 1.Go back to your DNS provider and add a TXT record:
nestsms-verify=abc123xyzxxxxxxxxxxxxxxxx| Type | Name (Host) | Value |
|---|---|---|
| TXT | _nestsms-verify.yourdomain.com | nestsms-verify=abc123xyzxxxxxxxxxxxxxxxx |
Wait for Verification
After adding the TXT record, the platform will automatically check for it every few minutes.
- DNS propagation can take 15 minutes to 48 hours depending on your provider.
- The status badge on the Organizations settings page will update:
- Pending - verification not yet confirmed
- Verified - domain ownership confirmed
Visit Your Custom Domain
Once your domain shows Verified, open a browser and navigate to your frontend URL:
https://app.yourdomain.comThe platform should load with your organization's branding applied.
Upload Favicon & Brand Logo
- Recommended size: 32×32 px or 64×64 px
- Accepted formats: .ico, .png
- Appears in the browser tab
- Favicon Brand Logo Recommended format: SVG or PNG (transparent background)
- Max file size: 500 KB
- Displayed in the top navigation bar of your white-labeled portal
- Recommended dimensions: height 40px, width proportional
You're Live! 🎉
Your white-label platform is fully set up. Here's a summary of what you've configured:
| Setting | Value |
|---|---|
| Frontend URL | https://app.yourdomain.com |
| Backend URL | https://api.yourdomain.com |
| Domain Status | ✅ Verified |
| Primary Color | ✅ Set |
| Brand Logo | ✅ Uploaded |
| Favicon | ✅ Uploaded |
Your clients can now access the platform through your custom domain with your full branding applied.
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| Domain stuck on Pending | DNS not propagated yet | Wait up to 48h; check at whatsmydns.net |
| CNAME not resolving | Cloudflare proxy enabled | Set CNAME to DNS only (grey cloud) |
| TXT record not found | Wrong record name | Ensure name starts with _nestsms-verify. |
| Logo not showing | File too large or wrong format | Use SVG/PNG under 500 KB |
| Site loads but no branding | Cache issue | Hard refresh (Ctrl+Shift+R) or clear browser cache |
Need Help?
Contact support at support@nestsms.com or open a ticket from your dashboard.
Send SMS through UI
Log In to Dashboard
Sign in to your NestSMS dashboard with your account credentials and open the messaging workspace.
Compose a Message
Go to the send page, enter your sender name, and write the message you want to deliver.
Select Recipients
Add numbers manually, upload a list, or choose a saved group, then review and send.
Bulk SMS File Format Guide
Everything you need to upload contacts and send personalized messages via CSV & XLSX
📄 Supported Formats
| Format | Extensions |
|---|---|
| CSV (Comma-Separated Values) | .csv |
| Excel Workbook | .xlsx.xls |
Required Column: Phone Number
Your file must contain a phone number column. The header can be any of the following (case-insensitive):
phonephone_numbermobilemobile_numbercellcell_numbertelephonecontactcontact_numbernumberrecipient_phonerecipient_numberto
Valid Phone Number Formats
All numbers must be Nepali mobile numbers (10 digits starting with 98 or 97).
| Input Format | Accepted? |
|---|---|
9841234567 | ✓ Yes |
+9779841234567 — prefix stripped automatically | ✓ Yes |
9779841234567 — prefix stripped automatically | ✓ Yes |
01-123456 (landline) | ✗ Skipped |
984123 (too short) | ✗ Skipped |
ℹ Note: Rows with invalid or empty phone numbers are silently skipped and reported in the response as skipped_count.
Optional Column: Name
If a name column is present, it is attached to each message as the recipient name. Any of these headers are accepted:
namefull_namefirst_namerecipient_namecustomer_namecontact_name
Template Variables
Any column in your file can be used as a placeholder in your message template using {columnName} or {{columnName}} syntax.
Column headers are lowercased and spaces replaced with underscores. So First Name → {first_name}.
Common Aliases
| Template Variable | Also Resolved From |
|---|---|
{name} | first_name, full_name, firstname, fullname |
{firstname} | first_name |
{lastname} | last_name |
{fullname} | full_name, name, first_name |
{phone} | phone_number, mobile, mobile_number, contact_number |
{mobile} | mobile_number, phone, phone_number |
{email} | email_address |
Tip: If a placeholder has no matching column in the row, it is left as-is in the message.
Examples
1. Simple — no variables
Every recipient gets the same message.
File
phone 9841234567 9867654321
Message Template
Your order has been placed.
Result
9841234567 → Your order has been placed. 9867654321 → Your order has been placed.
2. Personalized with name
Each recipient gets a personalized greeting using {name}.
File
phone,name 9841234567,Rishav 9867654321,Sita
Message Template
Hello {name}, your OTP is 4521.Result
9841234567 → Hello Rishav, your OTP is 4521. 9867654321 → Hello Sita, your OTP is 4521.
3. Multiple custom variables
Payment reminders with amount and due date per recipient.
CSV
phone,name,amount,due_date 9841234567,Rishav,5000,2025-04-01 9867654321,Sita,3200,2025-04-05
💬 Template
Dear {name}, your payment of Rs.{amount} is due on {due_date}.Result
9841234567 → Dear Rishav, your payment of Rs.5000 is due on 2025-04-01. 9867654321 → Dear Sita, your payment of Rs.3200 is due on 2025-04-05.
Rules & Limits
Header Row First
The first row must always be the header row.
First Sheet Only
Only the first sheet is read from XLSX/XLS files.
Invalid Rows Are Skipped
Rows with invalid phone numbers are skipped — they do not cause the entire batch to fail.
No Hard Row Limit
No hard row limit enforced by the parser, but your account's wallet balance must cover all valid recipients.
Case-Insensitive Headers
Phone, PHONE, and phone all work identically.
Contact Import File Format Guide
Upload your contacts in CSV or Excel format — fast, flexible, and forgiving
Supported File Types
| Format | Extension |
|---|---|
| CSV (Comma-Separated Values) | .csv |
| Excel Workbook | .xlsx |
| Excel 97-2003 Workbook | .xls |
File Size Limit: Maximum file size is 50 MB
Required Columns
Your file must contain both of the following columns. Column names are case-insensitive.
Name
REQUIREDnamefull_namefirst_namerecipient_namecustomer_namecontact_name
Phone
REQUIREDphonephone_numbermobilemobile_numbercellcell_numbertelephonecontactcontact_numbernumberrecipient_phoneto
ℹ️ Note: Any other columns in the file are ignored and will not cause errors.
Phone Number Format
All phone numbers must be valid Nepali mobile numbers.
| Format | Example | Valid? |
|---|---|---|
| 10 digits, starts with 98 | 9841234567 | ✓ Valid |
| 10 digits, starts with 97 | 9741234567 | ✓ Valid |
| With +977 country code | +9779841234567 | ✓ Valid |
| With 977 (no +) | 9779841234567 | ✓ Valid |
| Less than 10 digits | 984123 | ✗ Invalid |
| Does not start with 97 or 98 | 9541234567 | ✗ Invalid |
| Contains letters or symbols | 984-123-4567 | ✗ Invalid |
Auto-Strip: The +977 / 977 prefix is automatically stripped before saving.
CSV Example
Basic CSV
name,phone Ram Bahadur,9841234567 Sita Sharma,9801112233 Hari Prasad,+9779751234567 Krishna Thapa,9861111111
With extra columns (they'll be ignored)
name,phone,email,address Ram Bahadur,9841234567,ram@example.com,Kathmandu Sita Sharma,9801112233,sita@example.com,Pokhara
Excel Example
| name | phone |
|---|---|
| Ram Bahadur | 9841234567 |
| Sita Sharma | 9801112233 |
| Hari Prasad | 9751234567 |
Important: For Excel files, data must be on the first sheet. Other sheets are ignored.
What Gets Skipped
Rows are silently skipped (counted in "Skipped") if any of the following apply:
Blank Row
The row is completely empty
Empty Name
The name field is missing or blank
Invalid Phone
Phone number is missing or in an invalid format
Duplicate (Existing)
Phone already exists in your contacts
Duplicate (In File)
Same number appears twice — only the first occurrence is kept
Import Result
After a successful import, you will see:
Created
Number of new contacts successfully added to your list
Skipped
Rows not imported, with reasons shown per row (up to 10 displayed)
Quick Checklist
Check each item before uploading your file: