SMS Universal API
Cos'è SMS Universal
SMS Universal è il formato moderno e consigliato per l'invio di messaggi SMS tramite la piattaforma Qlara. Ogni richiesta HTTP corrisponde a un singolo messaggio verso un singolo destinatario, rendendo l'integrazione semplice e immediata.
Rispetto al formato Legacy, SMS Universal offre supporto nativo per placeholder, notifiche di consegna, modalità simulazione e programmazione dell'invio.
Endpoint
POST https://lora-api.agiletelecom.com/api/message-server/sms/send
Content-Type: application/json
Autenticazione: API Key (X-Api-Key) oppure Basic Auth (Authorization: Basic)
Campi della richiesta
| Campo | Tipo | Obbligatorio | Default | Descrizione |
|---|---|---|---|---|
destination | string | Sì | – | Numero di telefono destinatario in formato internazionale (es. +393401234567) |
sender | string | Sì | – | Mittente del messaggio. Può essere alfanumerico (max 11 caratteri, es. MyBrand) o numerico (es. +393401234567) |
body | string | Sì | – | Testo del messaggio SMS. La lunghezza massima dipende dalla codifica (vedi sezione Codifica testo) |
campaignId | string | No | – | Identificativo della campagna per raggruppare più messaggi. Massimo 255 caratteri |
messageId | string | No | (auto) | Identificativo personalizzato del messaggio per tracciamento. Se non fornito, viene generato automaticamente (UUID) |
udhData | string | No | – | User Data Header in formato esadecimale. Usato per messaggi binari o concatenazione manuale. Nella maggior parte dei casi non è necessario |
simulation | boolean | No | false | Se true, il messaggio viene elaborato e validato ma non inviato fisicamente. Utile per test e debug |
enableNotification | boolean | No | true | Se true, abilita le notifiche di consegna (delivery notification) verso il tuo URL di callback |
placeholders | object | No | – | Mappa chiave-valore per sostituire i placeholder nel testo. Es: {"nome": "Mario"} sostituisce {nome} nel campo body |
scheduledDate | string | No | – | Data e ora di invio programmato. Formato: yyyy-MM-dd HH:mm:ss.SSSZ (es. 2025-10-01 09:00:00.000+0000) |
skipRcsOverride | boolean | No | false | Se true, l'SMS viene inviato direttamente senza passare per un eventuale override RCS configurato a livello di account |
Risposta
Tutte le chiamate di invio restituiscono una risposta con questa struttura:
{
"messageId": "e76614d1-4ac1-4d94-89f0-d07f1b5a190c",
"simulation": false,
"results": {
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}
| Campo | Tipo | Descrizione |
|---|---|---|
messageId | string | ID univoco del messaggio. Usalo per correlare le notifiche di consegna |
simulation | boolean | true se il messaggio era in modalità simulazione |
results.sms.accepted | boolean | true se il messaggio è stato accettato per l'invio |
results.sms.unicode | boolean | true se il messaggio contiene caratteri Unicode (codifica UTF-16) |
results.sms.parts | integer | Numero di parti SMS in cui il messaggio è stato suddiviso |
results.sms.reasons | array | Lista di motivi in caso di rifiuto (vuota se accettato) |
Importante:
accepted: truesignifica che il messaggio è stato accettato per l'invio, non che è stato consegnato. La conferma di consegna arriva tramite il callback Delivery Notification.
Codifica del testo
GSM-7 (standard)
:::tip Risparmio sui costi Usa la codifica GSM-7 quando possibile: supporta fino a 160 caratteri per messaggio singolo, contro i 70 dell'Unicode. Evita emoji e caratteri non latini per mantenere i costi al minimo. :::
La codifica predefinita per gli SMS. Supporta lettere latine, numeri, simboli comuni e alcuni caratteri speciali.
- Messaggio singolo: fino a 160 caratteri
- Messaggio concatenato: ogni parte contiene 153 caratteri (7 caratteri riservati all'header di concatenazione)
Unicode / UTF-16
Utilizzata automaticamente quando il testo contiene caratteri non supportati da GSM-7 (es. emoji, caratteri cinesi, arabi, cirillici).
- Messaggio singolo: fino a 70 caratteri
- Messaggio concatenato: ogni parte contiene 67 caratteri (3 caratteri riservati all'header di concatenazione)
Regole di concatenazione
:::warning Limiti di caratteri e costi Un singolo SMS supporta 160 caratteri GSM-7 o 70 caratteri Unicode. Il superamento di questi limiti causa la suddivisione del messaggio in più parti, e ogni parte viene tariffata separatamente. :::
Quando il testo supera la lunghezza massima di una singola parte, il messaggio viene automaticamente suddiviso in più parti (concatenazione). Il dispositivo del destinatario ricompone le parti in un unico messaggio.
| Codifica | 1 parte | 2 parti | 3 parti | N parti |
|---|---|---|---|---|
| GSM-7 | 160 car. | 306 car. | 459 car. | N x 153 car. |
| Unicode | 70 car. | 134 car. | 201 car. | N x 67 car. |
Nota: Ogni parte SMS viene tariffata singolarmente. Il campo
results.sms.partsnella risposta indica il numero di parti.
Notifiche di consegna (Delivery Notification)
Se enableNotification è true, il sistema invia una callback HTTP POST al tuo URL quando lo stato del messaggio cambia.
Formato della 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"
}
| Campo | Tipo | Descrizione |
|---|---|---|
channel | string | Sempre "SMS" per questo canale |
eventType | string | Sempre "DELIVERY" |
messageId | string | ID del messaggio (lo stesso restituito nella risposta di invio) |
destination | string | Numero destinatario |
statusCode | integer | Codice di stato: 3 = consegnato, 6 = non consegnabile |
description | string | Descrizione testuale dello stato |
price | number | Costo della singola parte SMS |
numPart | integer | Numero della parte corrente (es. 1, 2, 3...) |
totalParts | integer | Numero totale di parti del messaggio |
eventDate | string | Data e ora dell'evento in formato ISO 8601 |
Codici di stato
| statusCode | Significato |
|---|---|
3 | Messaggio consegnato al dispositivo del destinatario |
6 | Messaggio non consegnabile (numero inesistente, telefono spento, ecc.) |
Esempi
1. Invio semplice
Invio base di un messaggio SMS:
- cURL
- Node.js
- Python
- PHP
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."
}'
const response = await fetch(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
{
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
destination: "+393401234567",
sender: "MyBrand",
body: "Ciao! Il tuo ordine #12345 è stato spedito.",
}),
}
);
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
headers={
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Ciao! Il tuo ordine #12345 è stato spedito.",
},
)
print(response.json())
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://lora-api.agiletelecom.com/api/message-server/sms/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"destination" => "+393401234567",
"sender" => "MyBrand",
"body" => "Ciao! Il tuo ordine #12345 è stato spedito.",
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Risposta:
{
"messageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"simulation": false,
"results": {
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}
2. Invio con placeholder
I placeholder nel campo body vengono sostituiti con i valori della mappa placeholders:
- cURL
- Node.js
- Python
- PHP
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
}'
const response = await fetch(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
{
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
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,
}),
}
);
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
headers={
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"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,
},
)
print(response.json())
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://lora-api.agiletelecom.com/api/message-server/sms/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"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,
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Il destinatario riceverà: "Ciao Mario, il tuo appuntamento è confermato per il 15/03/2025 alle 14:30."
3. Invio programmato
Programma l'invio del messaggio a una data e ora futura:
- cURL
- Node.js
- Python
- PHP
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"
}'
const response = await fetch(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
{
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
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",
}),
}
);
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
headers={
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"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",
},
)
print(response.json())
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://lora-api.agiletelecom.com/api/message-server/sms/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"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",
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
4. Modalità simulazione
Il messaggio viene validato ma non inviato. Utile per verificare la correttezza della richiesta prima dell'invio reale:
- cURL
- Node.js
- Python
- PHP
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
}'
const response = await fetch(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
{
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
destination: "+393401234567",
sender: "MyBrand",
body: "Questo è un messaggio di test con emoji 🎉",
simulation: true,
}),
}
);
const data = await response.json();
console.log(data);
import requests
response = requests.post(
"https://lora-api.agiletelecom.com/api/message-server/sms/send",
headers={
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"destination": "+393401234567",
"sender": "MyBrand",
"body": "Questo è un messaggio di test con emoji 🎉",
"simulation": True,
},
)
print(response.json())
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://lora-api.agiletelecom.com/api/message-server/sms/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"destination" => "+393401234567",
"sender" => "MyBrand",
"body" => "Questo è un messaggio di test con emoji 🎉",
"simulation" => true,
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Risposta:
{
"messageId": "f1e2d3c4-b5a6-7890-abcd-ef0987654321",
"simulation": true,
"results": {
"sms": {
"accepted": true,
"unicode": true,
"parts": 1,
"reasons": []
}
}
}
Nota: La risposta indica
unicode: trueperché il messaggio contiene un'emoji.
Codici di errore
| Codice HTTP | Significato | Cosa fare |
|---|---|---|
200 | Richiesta elaborata | Controlla results.sms.accepted per verificare l'accettazione |
400 | Richiesta non valida | Verifica i campi obbligatori (destination, sender, body) e il formato dei dati |
401 | Non autenticato | Verifica la tua API Key o le credenziali Basic Auth |
403 | Accesso negato | IP non in whitelist o risorsa non autorizzata |
404 | Non trovato | Endpoint non corretto |
422 | Entità non processabile | I dati sono sintatticamente corretti ma semanticamente non validi (es. numero destinatario non valido) |
429 | Troppe richieste | Hai superato il rate limit. Riprova dopo il periodo indicato nell'header Retry-After |
500 | Errore interno del server | Riprova più tardi. Se il problema persiste, contatta il supporto |