RSMS Gateway API Documentation

Version: 1.0
Last Updated: January 3, 2026
Base URL: https://api.rsms.no


Table of Contents

  1. Overview
  2. Authentication
  3. Rate Limits
  4. API Endpoints
  5. Request Format
  6. Response Format
  7. Error Codes
  8. Examples
  9. Best Practices
  10. Support

Overview

The SMS Gateway API allows you to send SMS messages to multiple recipients through a simple REST API. The gateway handles message delivery and provides comprehensive logging and statistics for billing purposes.

Key Features

API Characteristics


Authentication

All API requests must include an authentication header with your API key.

Header Format

Authorization: Bearer YOUR_API_KEY

Example

curl -X POST https://api.rsms.no/api/v1/sms/send \
  -H "Authorization: Bearer sk_live_abc123xyz456..." \
  -H "Content-Type: application/json"

API Key Format

Security Notes

⚠️ Important:


Rate Limits

To ensure fair usage and system stability, the following rate limits apply:

Limits Per API Key

Limits Per Tag

Rate Limit Headers

Every response includes rate limit information:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704276000

Handling Rate Limits

When you exceed rate limits, you'll receive a 429 Too Many Requests response:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 60 seconds",
    "retryAfter": 60
  }
}

Best Practice: Implement exponential backoff when receiving 429 responses.


API Endpoints

Send SMS

Endpoint: POST /api/v1/sms/send

Send SMS messages to one or more recipients.

Request Headers

Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Request Body

{
  "tag": "R-COV1RSMS",
  "recipients": ["+4712345678", "+4787654321"],
  "message": "Your message content here"
}

Request Parameters

| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------------------------------------------- |
| tag | string | Yes | Tag code for sender identification (e.g., "R-COV1RSMS" → "RSMS") |
| recipients | array | Yes | Array of phone numbers in E.164 format (1-1000 recipients) |
| message | string | Yes | Message content (1-1000 characters) |


Request Format

Tag Parameter

The tag parameter identifies which sender name to use. Each tag maps to a specific sender name configured in your account.

Rules:

Example Tag Mappings:

"R-COV1RSMS" → "RSMS"
"MH-USV1RSMS" → "Microhard"
"GP-IRV2RSMS" → "Gapple Inc"

Recipients Parameter

Phone numbers must be in E.164 international format.

Format: +[Country Code][Number]

Valid Examples:

[
  "+4712345678", // Norway
  "+46701234567", // Sweden
  "+4407123456789", // UK
  "+12025551234" // USA
]

Invalid Examples:

[
  "12345678", // ❌ Missing country code
  "+47 123 45 678", // ❌ Contains spaces
  "0047123456789" // ❌ Wrong format
]

Rules:

Message Parameter

Character Limits:

SMS Segmentation:

The gateway automatically handles message splitting:

| Message Type | Single SMS | Multi-part SMS |
| -------------------- | ---------- | ----------------- |
| Standard (GSM-7) | 160 chars | 153 chars/segment |
| Unicode | 70 chars | 67 chars/segment |

Examples:

Supported Characters:


Response Format

Success Response

HTTP Status: 200 OK

{
  "success": true,
  "data": {
    "messageId": "550e8400-e29b-41d4-a716-446655440000",
    "recipientCount": 2,
    "smsSegmentsPerRecipient": 1,
    "totalSmsSegments": 2,
    "estimatedCost": "1.34 NOK",
    "status": "queued",
    "timestamp": "2026-01-03T10:30:00.000Z"
  }
}

Response Fields

| Field | Type | Description |
| ------------------------- | ------- | --------------------------------------------------- |
| success | boolean | Always true for successful requests |
| messageId | string | Unique UUID for this SMS transaction |
| recipientCount | integer | Number of recipients (after deduplication) |
| smsSegmentsPerRecipient | integer | SMS segments required per recipient |
| totalSmsSegments | integer | Total segments sent (recipientCount × segments) |
| estimatedCost | string | Estimated cost (for reference only) |
| status | string | Always "queued" (messages are queued immediately) |
| timestamp | string | ISO 8601 timestamp of when message was queued |

Error Response

HTTP Status: 4xx or 5xx

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": ["Additional context (optional)"]
  }
}

Error Codes

Client Errors (4xx)

| Code | HTTP Status | Description | Action Required |
| --------------------- | ----------- | --------------------------------- | ----------------------------------------- |
| UNAUTHORIZED | 401 | Invalid or missing API key | Check your API key |
| FORBIDDEN | 403 | Valid key but lacks permission | Contact administrator |
| IP_NOT_WHITELISTED | 403 | Request from unauthorized IP | Add your IP to whitelist or contact admin |
| INVALID_TAG | 400 | Tag not found or inactive | Use a valid tag from your account |
| VALIDATION_ERROR | 400 | Invalid request payload | Fix validation errors in request |
| RECIPIENT_BLOCKED | 400 | Recipient blocked by SMS provider | Remove blocked recipient |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests | Wait and retry with backoff |

Server Errors (5xx)

| Code | HTTP Status | Description | Action Required |
| --------------------- | ----------- | ------------------------------- | ------------------------------------------ |
| PROVIDER_ERROR | 500 | SMS provider error | Retry request; contact support if persists |
| PROVIDER_AUTH_ERROR | 500 | Provider authentication failed | Contact administrator |
| PROVIDER_NO_CREDITS | 500 | Provider account out of credits | Contact administrator |
| INTERNAL_ERROR | 500 | Unexpected server error | Retry request; contact support if persists |

Error Response Examples

401 - Invalid API Key

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key"
  }
}

400 - Validation Error

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request payload",
    "details": [
      "recipients[0]: Invalid phone number format '+47abc'",
      "message: Message exceeds maximum length of 1000 characters"
    ]
  }
}

403 - IP Not Whitelisted

{
  "success": false,
  "error": {
    "code": "IP_NOT_WHITELISTED",
    "message": "Request from unauthorized IP address"
  }
}

429 - Rate Limit Exceeded

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 60 seconds",
    "retryAfter": 60
  }
}

Examples

Example 1: Send to Single Recipient

Request:

curl -X POST https://api.rsms.no/api/v1/sms/send \
  -H "Authorization: Bearer sk_live_abc123xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "tag": "RS-COV1RSMS",
    "recipients": ["+4712345678"],
    "message": "Hello! This is a test message from our system."
  }'

Response:

{
  "success": true,
  "data": {
    "messageId": "550e8400-e29b-41d4-a716-446655440000",
    "recipientCount": 1,
    "smsSegmentsPerRecipient": 1,
    "totalSmsSegments": 1,
    "estimatedCost": "0.67 NOK",
    "status": "queued",
    "timestamp": "2026-01-03T10:30:00.000Z"
  }
}

Example 2: Send to Multiple Recipients

Request:

curl -X POST https://api.rsms.no/api/v1/sms/send \
  -H "Authorization: Bearer sk_live_abc123xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "tag": "RS-COV1RSMS",
    "recipients": [
      "+4712345678",
      "+4787654321",
      "+46701234567"
    ],
    "message": "Important notification: Your account has been updated."
  }'

Response:

{
  "success": true,
  "data": {
    "messageId": "660e8400-e29b-41d4-a716-446655440001",
    "recipientCount": 3,
    "smsSegmentsPerRecipient": 1,
    "totalSmsSegments": 3,
    "estimatedCost": "2.01 NOK",
    "status": "queued",
    "timestamp": "2026-01-03T10:31:00.000Z"
  }
}

Example 3: Long Message (Multiple Segments)

Request:

curl -X POST https://api.rsms.no/api/v1/sms/send \
  -H "Authorization: Bearer sk_live_abc123xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "tag": "billing",
    "recipients": ["+4712345678"],
    "message": "Dear customer, this is a reminder that your invoice #12345 for the amount of 500 NOK is due on January 15, 2026. Please make payment before the due date to avoid late fees. You can pay via our website, mobile app, or bank transfer. For questions, contact support@example.com. Thank you!"
  }'

Response:

{
  "success": true,
  "data": {
    "messageId": "770e8400-e29b-41d4-a716-446655440002",
    "recipientCount": 1,
    "smsSegmentsPerRecipient": 2,
    "totalSmsSegments": 2,
    "estimatedCost": "1.34 NOK",
    "status": "queued",
    "timestamp": "2026-01-03T10:32:00.000Z"
  }
}

Example 4: JavaScript/Node.js

const axios = require("axios");

async function sendSMS(tag, recipients, message) {
  try {
    const response = await axios.post(
      "https://api.rsms.no/api/v1/sms/send",
      {
        tag: tag,
        recipients: recipients,
        message: message,
      },
      {
        headers: {
          Authorization: `Bearer ${process.env.SMS_API_KEY}`,
          "Content-Type": "application/json",
        },
      },
    );

    console.log("SMS sent successfully:", response.data);
    return response.data;
  } catch (error) {
    if (error.response) {
      // Server responded with error
      console.error("Error:", error.response.data.error);

      // Handle rate limiting
      if (error.response.status === 429) {
        const retryAfter = error.response.data.error.retryAfter;
        console.log(`Rate limited. Retry after ${retryAfter} seconds`);
      }
    } else {
      console.error("Network error:", error.message);
    }
    throw error;
  }
}

// Usage
sendSMS(
  "RS-COV1RSMS",
  ["+4712345678", "+4787654321"],
  "Hello from our application!",
);

Example 5: Python

import requests
import os
import time

API_KEY = os.environ.get('SMS_API_KEY')
BASE_URL = 'https://api.rsms.no/api/v1'

def send_sms(tag, recipients, message, max_retries=3):
    """
    Send SMS with retry logic for rate limiting
    """
    headers = {
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    }

    payload = {
        'tag': tag,
        'recipients': recipients,
        'message': message
    }

    for attempt in range(max_retries):
        try:
            response = requests.post(
                f'{BASE_URL}/sms/send',
                json=payload,
                headers=headers,
                timeout=30
            )

            # Success
            if response.status_code == 200:
                data = response.json()
                print(f"SMS sent successfully: {data['data']['messageId']}")
                return data

            # Rate limited - retry with backoff
            elif response.status_code == 429:
                error_data = response.json()
                retry_after = error_data['error'].get('retryAfter', 60)
                print(f"Rate limited. Waiting {retry_after} seconds...")
                time.sleep(retry_after)
                continue

            # Other errors
            else:
                error_data = response.json()
                print(f"Error: {error_data['error']['message']}")
                raise Exception(error_data['error']['code'])

        except requests.exceptions.RequestException as e:
            print(f"Network error: {e}")
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff
                print(f"Retrying in {wait_time} seconds...")
                time.sleep(wait_time)
            else:
                raise

    raise Exception("Max retries exceeded")

# Usage
send_sms(
    tag='RS-COV1RSMS',
    recipients=['+4712345678', '+4787654321'],
    message='Hello from our Python application!'
)

Example 6: PHP

<?php

function sendSMS($tag, $recipients, $message) {
    $apiKey = getenv('SMS_API_KEY');
    $url = 'https://api.rsms.no/api/v1/sms/send';

    $data = [
        'tag' => $tag,
        'recipients' => $recipients,
        'message' => $message
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $responseData = json_decode($response, true);

    if ($httpCode === 200) {
        echo "SMS sent successfully: " . $responseData['data']['messageId'] . "\n";
        return $responseData;
    } else {
        echo "Error: " . $responseData['error']['message'] . "\n";

        // Handle rate limiting
        if ($httpCode === 429) {
            $retryAfter = $responseData['error']['retryAfter'];
            echo "Rate limited. Retry after $retryAfter seconds\n";
        }

        throw new Exception($responseData['error']['code']);
    }
}

// Usage
sendSMS(
    'RS-COV1RSMS',
    ['+4712345678', '+4787654321'],
    'Hello from our PHP application!'
);
?>

Best Practices

1. Error Handling

Always implement proper error handling:

// ✅ GOOD - Handles all error cases
try {
  const response = await sendSMS(tag, recipients, message);
  console.log("Success:", response.data.messageId);
} catch (error) {
  if (error.response?.status === 429) {
    // Rate limited - implement backoff
    await sleep(error.response.data.error.retryAfter * 1000);
    return retry();
  } else if (error.response?.status >= 500) {
    // Server error - retry with exponential backoff
    return retryWithBackoff();
  } else {
    // Client error - log and alert
    logger.error("SMS failed:", error.response.data);
    alertAdmin(error);
  }
}

// ❌ BAD - No error handling
const response = await sendSMS(tag, recipients, message);
console.log("Sent!");

2. Rate Limiting

Implement client-side rate limiting to avoid hitting API limits:

// Token bucket algorithm example
class RateLimiter {
  constructor(tokensPerMinute) {
    this.tokens = tokensPerMinute;
    this.maxTokens = tokensPerMinute;
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refill();

    if (this.tokens > 0) {
      this.tokens--;
      return true;
    }

    // Wait for token refill
    const waitTime = 60000 / this.maxTokens;
    await sleep(waitTime);
    return this.acquire();
  }

  refill() {
    const now = Date.now();
    const timePassed = now - this.lastRefill;
    const tokensToAdd = (timePassed / 60000) * this.maxTokens;

    this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
}

// Usage
const limiter = new RateLimiter(90); // Stay under 100/min limit
await limiter.acquire();
await sendSMS(tag, recipients, message);

3. Batching Large Recipient Lists

For large recipient lists, split into batches:

async function sendToMany(tag, allRecipients, message) {
  const BATCH_SIZE = 1000; // API maximum
  const batches = [];

  for (let i = 0; i < allRecipients.length; i += BATCH_SIZE) {
    batches.push(allRecipients.slice(i, i + BATCH_SIZE));
  }

  const results = [];
  for (const batch of batches) {
    const result = await sendSMS(tag, batch, message);
    results.push(result);

    // Small delay between batches to respect rate limits
    if (batches.length > 1) {
      await sleep(1000);
    }
  }

  return results;
}

4. Phone Number Validation

Validate phone numbers before sending:

function validatePhoneNumber(phone) {
  // E.164 format: +[country code][number]
  const e164Regex = /^\+[1-9]\d{1,14}$/;

  if (!e164Regex.test(phone)) {
    throw new Error(`Invalid phone number format: ${phone}`);
  }

  return true;
}

// Validate all recipients before API call
const validRecipients = recipients.filter((phone) => {
  try {
    return validatePhoneNumber(phone);
  } catch (e) {
    console.warn(`Skipping invalid number: ${phone}`);
    return false;
  }
});

if (validRecipients.length === 0) {
  throw new Error("No valid recipients");
}

5. Message Length Optimization

Calculate segments before sending to estimate costs:

function calculateSmsSegments(message) {
  // Check if message contains Unicode characters
  const isUnicode = /[^\x00-\x7F]/.test(message);
  const length = message.length;

  if (isUnicode) {
    if (length <= 70) return 1;
    return Math.ceil(length / 67);
  } else {
    if (length <= 160) return 1;
    return Math.ceil(length / 153);
  }
}

const segments = calculateSmsSegments(message);
const totalSegments = segments * recipients.length;
console.log(`This will use ${totalSegments} SMS segments`);

6. Secure API Key Storage

Never hardcode API keys:

// ❌ BAD - Hardcoded key
const API_KEY = "sk_live_abc123xyz456";

// ✅ GOOD - Environment variable
const API_KEY = process.env.SMS_API_KEY;

// ✅ GOOD - Secret manager (AWS, Azure, GCP)
const API_KEY = await secretManager.getSecret("sms-api-key");

7. Logging and Monitoring

Log all SMS operations for debugging and auditing:

async function sendSMS(tag, recipients, message) {
  const requestId = generateUUID();

  logger.info("SMS request started", {
    requestId,
    tag,
    recipientCount: recipients.length,
    messageLength: message.length,
  });

  try {
    const response = await apiCall(tag, recipients, message);

    logger.info("SMS request successful", {
      requestId,
      messageId: response.data.messageId,
      segments: response.data.totalSmsSegments,
    });

    return response;
  } catch (error) {
    logger.error("SMS request failed", {
      requestId,
      errorCode: error.response?.data?.error?.code,
      errorMessage: error.response?.data?.error?.message,
    });

    throw error;
  }
}

8. Retry Strategy

Implement exponential backoff for server errors:

async function sendWithRetry(tag, recipients, message, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await sendSMS(tag, recipients, message);
    } catch (error) {
      const shouldRetry =
        error.response?.status >= 500 ||
        error.code === "ECONNRESET" ||
        error.code === "ETIMEDOUT";

      if (!shouldRetry || attempt === maxRetries - 1) {
        throw error;
      }

      const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await sleep(delay);
    }
  }
}

9. Testing

Test with different scenarios before production:

// Test cases to implement
async function runTests() {
  // 1. Valid request
  await sendSMS("RS-COV1RSMS", ["+4712345678"], "Test message");

  // 2. Multiple recipients
  await sendSMS("RS-COV1RSMS", ["+4712345678", "+4787654321"], "Test");

  // 3. Long message
  await sendSMS("RS-COV1RSMS", ["+4712345678"], "A".repeat(200));

  // 4. Invalid phone number (should fail)
  try {
    await sendSMS("RS-COV1RSMS", ["12345678"], "Test");
  } catch (e) {
    console.log("Expected error:", e.message);
  }

  // 5. Invalid tag (should fail)
  try {
    await sendSMS("invalid-tag", ["+4712345678"], "Test");
  } catch (e) {
    console.log("Expected error:", e.message);
  }
}

Support

Getting Help

If you encounter issues or have questions:

  1. Check this documentation - Most common questions are answered here
  2. Review error codes - Error messages provide specific guidance
  3. Check rate limits - Ensure you're not exceeding rate limits
  4. Contact support - Email: sms@rosario.no

Support Hours

Feedback

We value your feedback! Please let us know:

Email: sms@rosario.no


Changelog

Version 1.0 (January 3, 2026)


Legal

Terms of Service

By using this API, you agree to:

Data Privacy

SLA (Service Level Agreement)


Last Updated: January 3, 2026
API Version: 1.0
Documentation Version: 1.0