16 KiB
SaaS Application Integration Guide
Overview
This guide shows how to integrate MySMSAPio into your SaaS application to send SMS and OTP messages.
Prerequisites
- MySMSAPio Server: Running and accessible (e.g.,
http://192.168.18.66:3005) - API Key: Client API key (starts with
api_live_...) - Gateway Device: At least one Android gateway online
Quick Start
1. Create an API Key
Via Admin Interface:
- Go to
http://your-server:3005/admin/api_keys - Click "New API Key"
- Enter name (e.g., "My SaaS App")
- Enable permissions:
send_sms,receive_sms - Copy the API key (shown only once!)
Via Rails Console:
result = ApiKey.generate!(
name: "My SaaS App",
permissions: {
send_sms: true,
receive_sms: true,
send_otp: true,
verify_otp: true
}
)
puts result[:raw_key]
# Save this key: api_live_abc123...
2. Test Connection
curl -X GET http://your-server:3005/up \
-H "Authorization: Bearer api_live_your_key_here"
Expected: 200 OK
API Endpoints
Base URL
http://your-server:3005/api/v1
Authentication
All requests require the Authorization header:
Authorization: Bearer api_live_your_key_here
Sending SMS
Endpoint
POST /api/v1/sms/send
Request
{
"to": "+959123456789",
"message": "Your message here"
}
Response (Success - 202 Accepted)
{
"success": true,
"message_id": "msg_abc123def456...",
"status": "queued"
}
Response (Error - 422 Unprocessable Entity)
{
"error": "Invalid phone number format"
}
Example: cURL
curl -X POST http://your-server:3005/api/v1/sms/send \
-H "Authorization: Bearer api_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"to": "+959123456789",
"message": "Hello from My SaaS App!"
}'
Example: JavaScript/Node.js
const axios = require('axios');
async function sendSMS(phoneNumber, message) {
try {
const response = await axios.post(
'http://your-server:3005/api/v1/sms/send',
{
to: phoneNumber,
message: message
},
{
headers: {
'Authorization': 'Bearer api_live_your_key_here',
'Content-Type': 'application/json'
}
}
);
console.log('SMS sent!');
console.log('Message ID:', response.data.message_id);
console.log('Status:', response.data.status);
return response.data.message_id;
} catch (error) {
console.error('Failed to send SMS:', error.response?.data || error.message);
throw error;
}
}
// Usage
sendSMS('+959123456789', 'Welcome to our service!')
.then(messageId => console.log('Sent:', messageId))
.catch(err => console.error('Error:', err));
Example: Python
import requests
def send_sms(phone_number, message):
url = "http://your-server:3005/api/v1/sms/send"
headers = {
"Authorization": "Bearer api_live_your_key_here",
"Content-Type": "application/json"
}
data = {
"to": phone_number,
"message": message
}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 202:
result = response.json()
print(f"SMS sent! Message ID: {result['message_id']}")
return result['message_id']
else:
print(f"Failed: {response.json()}")
raise Exception(response.json().get('error', 'Unknown error'))
# Usage
message_id = send_sms("+959123456789", "Welcome to our service!")
Example: PHP
<?php
function sendSMS($phoneNumber, $message) {
$url = 'http://your-server:3005/api/v1/sms/send';
$data = [
'to' => $phoneNumber,
'message' => $message
];
$options = [
'http' => [
'header' => [
"Authorization: Bearer api_live_your_key_here",
"Content-Type: application/json"
],
'method' => 'POST',
'content' => json_encode($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) {
throw new Exception('Failed to send SMS');
}
$response = json_decode($result, true);
echo "SMS sent! Message ID: " . $response['message_id'] . "\n";
return $response['message_id'];
}
// Usage
$messageId = sendSMS("+959123456789", "Welcome to our service!");
?>
Example: Ruby
require 'httparty'
def send_sms(phone_number, message)
response = HTTParty.post(
'http://your-server:3005/api/v1/sms/send',
headers: {
'Authorization' => 'Bearer api_live_your_key_here',
'Content-Type' => 'application/json'
},
body: {
to: phone_number,
message: message
}.to_json
)
if response.code == 202
puts "SMS sent! Message ID: #{response['message_id']}"
response['message_id']
else
puts "Failed: #{response['error']}"
raise StandardError, response['error']
end
end
# Usage
message_id = send_sms("+959123456789", "Welcome to our service!")
Sending OTP
Endpoint
POST /api/v1/otp/send
Request
{
"phone_number": "+959123456789"
}
Response (Success - 200 OK)
{
"success": true,
"message": "OTP sent successfully",
"expires_in": 300
}
Response (Error - Rate Limited)
{
"error": "Rate limit exceeded. Maximum 3 OTP per hour for this number"
}
Example: JavaScript
async function sendOTP(phoneNumber) {
try {
const response = await axios.post(
'http://your-server:3005/api/v1/otp/send',
{
phone_number: phoneNumber
},
{
headers: {
'Authorization': 'Bearer api_live_your_key_here',
'Content-Type': 'application/json'
}
}
);
console.log('OTP sent!');
console.log('Expires in:', response.data.expires_in, 'seconds');
return true;
} catch (error) {
console.error('Failed to send OTP:', error.response?.data || error.message);
throw error;
}
}
// Usage
sendOTP('+959123456789')
.then(() => console.log('OTP sent successfully'))
.catch(err => console.error('Error:', err));
Example: Python
def send_otp(phone_number):
url = "http://your-server:3005/api/v1/otp/send"
headers = {
"Authorization": "Bearer api_live_your_key_here",
"Content-Type": "application/json"
}
data = {
"phone_number": phone_number
}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
result = response.json()
print(f"OTP sent! Expires in {result['expires_in']} seconds")
return True
else:
print(f"Failed: {response.json()}")
raise Exception(response.json().get('error', 'Unknown error'))
# Usage
send_otp("+959123456789")
Verifying OTP
Endpoint
POST /api/v1/otp/verify
Request
{
"phone_number": "+959123456789",
"code": "123456"
}
Response (Success - 200 OK)
{
"success": true,
"message": "OTP verified successfully"
}
Response (Error - Invalid)
{
"error": "Invalid or expired OTP"
}
Example: JavaScript
async function verifyOTP(phoneNumber, code) {
try {
const response = await axios.post(
'http://your-server:3005/api/v1/otp/verify',
{
phone_number: phoneNumber,
code: code
},
{
headers: {
'Authorization': 'Bearer api_live_your_key_here',
'Content-Type': 'application/json'
}
}
);
console.log('OTP verified!');
return true;
} catch (error) {
console.error('OTP verification failed:', error.response?.data || error.message);
return false;
}
}
// Usage
const isValid = await verifyOTP('+959123456789', '123456');
if (isValid) {
console.log('User verified!');
} else {
console.log('Invalid OTP');
}
Example: Python
def verify_otp(phone_number, code):
url = "http://your-server:3005/api/v1/otp/verify"
headers = {
"Authorization": "Bearer api_live_your_key_here",
"Content-Type": "application/json"
}
data = {
"phone_number": phone_number,
"code": code
}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
print("OTP verified successfully!")
return True
else:
print(f"Verification failed: {response.json()}")
return False
# Usage
if verify_otp("+959123456789", "123456"):
print("User authenticated!")
else:
print("Authentication failed")
Checking Message Status
Endpoint
GET /api/v1/sms/status/:message_id
Response
{
"message_id": "msg_abc123def456...",
"status": "delivered",
"sent_at": "2025-10-22T10:30:00Z",
"delivered_at": "2025-10-22T10:30:05Z",
"failed_at": null,
"error_message": null
}
Status Values
queued- Waiting for gatewaypending- No gateway available, will retrysent- Sent to gateway devicedelivered- Successfully delivered to recipientfailed- Failed to send
Example: JavaScript
async function checkMessageStatus(messageId) {
try {
const response = await axios.get(
`http://your-server:3005/api/v1/sms/status/${messageId}`,
{
headers: {
'Authorization': 'Bearer api_live_your_key_here'
}
}
);
console.log('Status:', response.data.status);
console.log('Sent at:', response.data.sent_at);
console.log('Delivered at:', response.data.delivered_at);
return response.data;
} catch (error) {
console.error('Failed to check status:', error.response?.data || error.message);
throw error;
}
}
// Usage
const status = await checkMessageStatus('msg_abc123def456');
Complete Integration Example
User Registration with OTP (JavaScript)
const MySMSAPI = {
baseURL: 'http://your-server:3005/api/v1',
apiKey: 'api_live_your_key_here',
headers() {
return {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
};
},
async sendSMS(phoneNumber, message) {
const response = await axios.post(
`${this.baseURL}/sms/send`,
{ to: phoneNumber, message },
{ headers: this.headers() }
);
return response.data;
},
async sendOTP(phoneNumber) {
const response = await axios.post(
`${this.baseURL}/otp/send`,
{ phone_number: phoneNumber },
{ headers: this.headers() }
);
return response.data;
},
async verifyOTP(phoneNumber, code) {
try {
const response = await axios.post(
`${this.baseURL}/otp/verify`,
{ phone_number: phoneNumber, code },
{ headers: this.headers() }
);
return response.data.success;
} catch (error) {
return false;
}
},
async checkStatus(messageId) {
const response = await axios.get(
`${this.baseURL}/sms/status/${messageId}`,
{ headers: this.headers() }
);
return response.data;
}
};
// Usage Example: User Registration Flow
async function registerUser(phoneNumber, userData) {
try {
// Step 1: Send OTP
console.log('Sending OTP...');
await MySMSAPI.sendOTP(phoneNumber);
console.log('OTP sent! Check your phone.');
// Step 2: Wait for user to enter OTP (in real app, this would be a form)
const userEnteredCode = '123456'; // Get from user input
// Step 3: Verify OTP
console.log('Verifying OTP...');
const isValid = await MySMSAPI.verifyOTP(phoneNumber, userEnteredCode);
if (isValid) {
console.log('✅ Phone verified!');
// Step 4: Create user account
const user = await createUserInDatabase(phoneNumber, userData);
// Step 5: Send welcome SMS
const result = await MySMSAPI.sendSMS(
phoneNumber,
`Welcome to our service, ${userData.name}! Your account is now active.`
);
console.log('Welcome SMS sent:', result.message_id);
return { success: true, user };
} else {
console.log('❌ Invalid OTP');
return { success: false, error: 'Invalid OTP' };
}
} catch (error) {
console.error('Registration failed:', error);
return { success: false, error: error.message };
}
}
// Example usage
registerUser('+959123456789', { name: 'John Doe', email: 'john@example.com' });
Error Handling
Common Errors
| Status | Error | Cause | Solution |
|---|---|---|---|
| 401 | Unauthorized | Invalid API key | Check API key is correct |
| 422 | Unprocessable Entity | Invalid phone number | Use international format (+959...) |
| 429 | Too Many Requests | Rate limit exceeded | Wait before retrying |
| 404 | Not Found | Message ID not found | Check message ID is correct |
| 503 | Service Unavailable | No gateway online | Ensure gateway is connected |
Rate Limits
- SMS Send: 100 per minute per API key
- OTP Send: 3 per hour per phone number
- OTP Verify: 3 attempts per OTP code
Best Practices
1. Store API Key Securely
// ❌ Don't hardcode in frontend
const apiKey = 'api_live_abc123...';
// ✅ Use environment variables
const apiKey = process.env.SMS_API_KEY;
// ✅ Store in backend config
const apiKey = config.get('sms.apiKey');
2. Handle Errors Gracefully
async function sendSMSWithRetry(phoneNumber, message, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await MySMSAPI.sendSMS(phoneNumber, message);
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(1000 * (i + 1)); // Exponential backoff
}
}
}
3. Validate Phone Numbers
function validatePhoneNumber(phone) {
// Myanmar phone numbers
const myanmarRegex = /^\+959\d{8,9}$/;
return myanmarRegex.test(phone);
}
4. Monitor Message Status
async function sendAndTrack(phoneNumber, message) {
const result = await MySMSAPI.sendSMS(phoneNumber, message);
// Poll status until delivered
const maxAttempts = 30; // 5 minutes (30 * 10 seconds)
for (let i = 0; i < maxAttempts; i++) {
await sleep(10000); // Wait 10 seconds
const status = await MySMSAPI.checkStatus(result.message_id);
if (status.status === 'delivered') {
console.log('✅ Message delivered!');
return status;
} else if (status.status === 'failed') {
console.log('❌ Message failed:', status.error_message);
throw new Error(status.error_message);
}
}
console.log('⏱️ Delivery timeout');
return null;
}
Testing
Test API Key
Use the API Tester in the admin interface:
http://your-server:3005/admin/api_tester
Test from Command Line
# Send SMS
curl -X POST http://your-server:3005/api/v1/sms/send \
-H "Authorization: Bearer api_live_your_key" \
-H "Content-Type: application/json" \
-d '{"to":"+959123456789","message":"Test"}'
# Send OTP
curl -X POST http://your-server:3005/api/v1/otp/send \
-H "Authorization: Bearer api_live_your_key" \
-H "Content-Type: application/json" \
-d '{"phone_number":"+959123456789"}'
# Verify OTP
curl -X POST http://your-server:3005/api/v1/otp/verify \
-H "Authorization: Bearer api_live_your_key" \
-H "Content-Type: application/json" \
-d '{"phone_number":"+959123456789","code":"123456"}'
Troubleshooting
Messages Stuck in "pending"
Cause: No gateway online Solution: Ensure at least one Android gateway is connected
"Invalid phone number" error
Cause: Wrong format
Solution: Use international format: +[country_code][number]
"Rate limit exceeded"
Cause: Too many requests Solution: Implement exponential backoff and caching
"Unauthorized" error
Cause: Invalid or expired API key Solution: Generate new API key in admin interface
Support
- API Documentation: See
API_DOCUMENTATION.md - Admin Interface:
http://your-server:3005/admin - API Tester:
http://your-server:3005/admin/api_tester - Logs:
http://your-server:3005/admin/logs
Summary
✅ Create API Key → Get api_live_... key
✅ Send SMS → POST /api/v1/sms/send
✅ Send OTP → POST /api/v1/otp/send
✅ Verify OTP → POST /api/v1/otp/verify
✅ Check Status → GET /api/v1/sms/status/:id
Your SaaS app is now ready to send SMS and OTP! 🚀