Skip to main content

SMS Universal API

What is SMS Universal

SMS Universal is the modern and recommended format for sending SMS messages through the Qlara platform. Each HTTP request corresponds to a single message to a single recipient, making integration simple and straightforward.

Compared to the Legacy format, SMS Universal offers native support for placeholders, delivery notifications, simulation mode, and scheduled sending.


Endpoint

POST https://lora-api.agiletelecom.com/api/message-server/sms/send

Content-Type: application/json

Authentication: API Key (X-Api-Key) or Basic Auth (Authorization: Basic)


Request fields

FieldTypeRequiredDefaultDescription
destinationstringYesRecipient phone number in international format (e.g., +393401234567)
senderstringYesMessage sender. Can be alphanumeric (max 11 characters, e.g., MyBrand) or numeric (e.g., +393401234567)
bodystringYesSMS message text. Maximum length depends on the encoding (see Text encoding section)
campaignIdstringNoCampaign identifier for grouping multiple messages. Maximum 255 characters
messageIdstringNo(auto)Custom message identifier for tracking. If not provided, it is automatically generated (UUID)
udhDatastringNoUser Data Header in hexadecimal format. Used for binary messages or manual concatenation. In most cases, this is not needed
simulationbooleanNofalseIf true, the message is processed and validated but not physically sent. Useful for testing and debugging
enableNotificationbooleanNotrueIf true, enables delivery notifications to your callback URL
placeholdersobjectNoKey-value map to replace placeholders in the text. E.g., {"name": "Mario"} replaces {name} in the body field
scheduledDatestringNoScheduled send date and time. Format: yyyy-MM-dd HH:mm:ss.SSSZ (e.g., 2025-10-01 09:00:00.000+0000)
skipRcsOverridebooleanNofalseIf true, the SMS is sent directly without going through any RCS override configured at the account level

Response

All send calls return a response with this structure:

Response
{
"messageId": "e76614d1-4ac1-4d94-89f0-d07f1b5a190c",
"simulation": false,
"results": {
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}
FieldTypeDescription
messageIdstringUnique message ID. Use it to correlate delivery notifications
simulationbooleantrue if the message was in simulation mode
results.sms.acceptedbooleantrue if the message was accepted for sending
results.sms.unicodebooleantrue if the message contains Unicode characters (UTF-16 encoding)
results.sms.partsintegerNumber of SMS parts the message was split into
results.sms.reasonsarrayList of rejection reasons (empty if accepted)

Important: accepted: true means the message was accepted for sending, not that it was delivered. Delivery confirmation arrives via the Delivery Notification callback.


Text encoding

GSM-7 (standard)

:::tip Cost savings Use GSM-7 encoding when possible: it supports up to 160 characters per single message, compared to 70 for Unicode. Avoid emoji and non-Latin characters to keep costs at a minimum. :::

The default encoding for SMS. Supports Latin letters, numbers, common symbols, and some special characters.

  • Single message: up to 160 characters
  • Concatenated message: each part contains 153 characters (7 characters reserved for the concatenation header)

Unicode / UTF-16

Used automatically when the text contains characters not supported by GSM-7 (e.g., emoji, Chinese, Arabic, Cyrillic characters).

  • Single message: up to 70 characters
  • Concatenated message: each part contains 67 characters (3 characters reserved for the concatenation header)

Concatenation rules

:::warning Character limits and costs A single SMS supports 160 GSM-7 characters or 70 Unicode characters. Exceeding these limits causes the message to be split into multiple parts, and each part is billed separately. :::

When the text exceeds the maximum length of a single part, the message is automatically split into multiple parts (concatenation). The recipient's device reassembles the parts into a single message.

Encoding1 part2 parts3 partsN parts
GSM-7160 chars306 chars459 charsN x 153 chars
Unicode70 chars134 chars201 charsN x 67 chars

Note: Each SMS part is billed separately. The results.sms.parts field in the response indicates the number of parts.


Delivery notifications (Delivery Notification)

If enableNotification is true, the system sends an HTTP POST callback to your URL when the message status changes.

Callback format

Delivery Notification Callback
{
"channel": "SMS",
"eventType": "DELIVERY",
"messageId": "e76614d1-4ac1-4d94-89f0-d07f1b5a190c",
"destination": "+393401234567",
"statusCode": 3,
"description": "delivered",
"price": 0.035,
"numPart": 1,
"totalParts": 1,
"eventDate": "2025-10-16T10:42:18Z"
}
FieldTypeDescription
channelstringAlways "SMS" for this channel
eventTypestringAlways "DELIVERY"
messageIdstringMessage ID (the same one returned in the send response)
destinationstringRecipient number
statusCodeintegerStatus code: 3 = delivered, 6 = undeliverable
descriptionstringTextual description of the status
pricenumberCost of the individual SMS part
numPartintegerCurrent part number (e.g., 1, 2, 3...)
totalPartsintegerTotal number of parts in the message
eventDatestringEvent date and time in ISO 8601 format

Status codes

statusCodeMeaning
3Message delivered to the recipient's device
6Message undeliverable (non-existent number, phone off, etc.)

Examples

1. Simple send

Basic SMS message send:

curl -X POST "https://lora-api.agiletelecom.com/api/message-server/sms/send" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Ciao! Il tuo ordine #12345 è stato spedito."
}'

Response:

{
"messageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"simulation": false,
"results": {
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}

2. Send with placeholders

Placeholders in the body field are replaced with values from the placeholders map:

curl -X POST "https://lora-api.agiletelecom.com/api/message-server/sms/send" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Ciao {nome}, il tuo appuntamento è confermato per il {data} alle {ora}.",
"placeholders": {
"nome": "Mario",
"data": "15/03/2025",
"ora": "14:30"
},
"enableNotification": true
}'

The recipient will receive: "Ciao Mario, il tuo appuntamento è confermato per il 15/03/2025 alle 14:30."

3. Scheduled send

Schedule the message to be sent at a future date and time:

curl -X POST "https://lora-api.agiletelecom.com/api/message-server/sms/send" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Promemoria: la tua prenotazione è domani alle 10:00.",
"scheduledDate": "2025-10-01 09:00:00.000+0000",
"campaignId": "reminder-ottobre-2025"
}'

4. Simulation mode

The message is validated but not sent. Useful for verifying the correctness of the request before actual sending:

curl -X POST "https://lora-api.agiletelecom.com/api/message-server/sms/send" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Questo è un messaggio di test con emoji 🎉",
"simulation": true
}'

Response:

{
"messageId": "f1e2d3c4-b5a6-7890-abcd-ef0987654321",
"simulation": true,
"results": {
"sms": {
"accepted": true,
"unicode": true,
"parts": 1,
"reasons": []
}
}
}

Note: The response shows unicode: true because the message contains an emoji.


Error codes

HTTP CodeMeaningWhat to do
200Request processedCheck results.sms.accepted to verify acceptance
400Bad requestVerify required fields (destination, sender, body) and data format
401Not authenticatedVerify your API Key or Basic Auth credentials
403Access deniedIP not in whitelist or unauthorized resource
404Not foundIncorrect endpoint
422Unprocessable entityData is syntactically correct but semantically invalid (e.g., invalid recipient number)
429Too many requestsYou have exceeded the rate limit. Retry after the period indicated in the Retry-After header
500Internal server errorRetry later. If the problem persists, contact support