# Agile Telecom — Complete API Documentation > This file contains the full technical documentation for all Agile Telecom APIs. > It is designed for AI assistants and LLMs to use as a knowledge base. > Last updated: 2026-04-08 --- --- title: API Documentation sidebar_label: Overview sidebar_position: 0 slug: / description: Agile Telecom API documentation — SMS, SMPP, RCS, and network services. --- # API Documentation Welcome to the Agile Telecom developer documentation. Here you'll find everything you need to integrate our messaging services into your application. ## SMS | Service | Description | |---------|-------------| | [SMS REST API](./sms-universal) | Modern REST API for sending SMS with placeholders, scheduling, and delivery webhooks. | | [SMS REST API (Legacy)](./sms-legacy) | Legacy HTTP API with Basic Auth and API Key authentication. Full feature set with 12+ usage examples. | | [SMPP Protocol](./smpp) | Direct SMPP connection for high-throughput SMS delivery. TX, RX, and TRX modes supported. | | [Inbound SMS](./inbound-sms) | Receive SMS via email forwarding or HTTP POST webhook. | ## Network Services | Service | Description | |---------|-------------| | [Credit Check](./credit-check) | Query your account balance via REST API. | | [MNP Lookup](./mnp) | Check mobile number portability status, current operator, and network details. | ## RCS | Service | Description | |---------|-------------| | [RCS API](./rcs) | Send rich messages with images, buttons, cards, and carousels. Full REST API reference. | | [RCS Templates](./rcs-templates) | Create and manage RCS message templates — TEXT, CARD, and CAROUSEL types. | --- :::tip Getting started If you're new to Agile Telecom APIs, start with the [SMS REST API](./sms-universal) guide. It includes a Postman Collection for quick testing. ::: ## Authentication All APIs use one of these authentication methods: - **API Key** — Pass your key via `X-Api-Key` header - **Basic Auth** — Username and password in the `Authorization` header - **OAuth 1.1** — For advanced integrations Your credentials are available in the [Agile Telecom portal](https://lora.agiletelecom.com/login). ## Need help? - **Technical support**: [Contact us](https://agiletelecom.com/contact-us/) - **API documentation (legacy)**: [docs.agiletelecom.com](https://agiletelecom.com/documentation) --- --- title: SMS REST API sidebar_label: SMS REST API sidebar_position: 1 description: Send SMS using Agile Telecom's modern REST API with support for placeholders, scheduled delivery, simulation mode, and comprehensive delivery notifications. --- ``` The SMS REST API lets you send text messages to any mobile number worldwide through a single HTTPS endpoint. It handles encoding, multipart splitting, and delivery tracking automatically — you provide the destination, sender ID, and message body. Download the [SMS Universal Postman Collection](https://drive.google.com/drive/folders/1H_uRui_3jdIXDutI5iqwr54D87yehFR8?usp=sharing). ## Quick Start The fastest way to send an SMS: make a POST request with your API key, destination number, sender, and message text. The API returns a `messageId` you can use to track delivery. ### Base URL ``` https://lora-api.agiletelecom.com ``` ### Authentication Every request requires an API key passed in the `X-Api-Key` header. Generate your key from the [Agile Telecom portal](https://lora.agiletelecom.com/login) under **Settings > API Keys**. | Header | Required | Description | |--------|----------|-------------| | `Content-Type` | Yes | Must be `application/json` | | `X-Api-Key` | Yes | Your API key from the portal | ### Environment Variables These variables are used throughout the examples. Replace them with your actual values. | Variable | Example | Description | |----------|---------|-------------| | `BASE_URL` | `https://lora-api.agiletelecom.com` | API base URL | | `API_KEY` | `ak_live_*****` | Your API key | | `DESTINATION` | `+393471234567` | Recipient number in international format (with country code) | | `SENDER` | `MyCompany` | Alphanumeric (max 11 chars) or numeric (max 16 digits), registered on the portal | ## Send an SMS Send a single text message to a phone number. This is the most common operation — it covers 90% of integration use cases. **Endpoint:** `POST /api/message-server/sms/send` ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "destination": "+393471234567", "sender": "MyCompany", "body": "Hello from Agile Telecom SMS API!" }' ``` ``` ```python response = requests.post( "https://lora-api.agiletelecom.com/api/message-server/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "destination": "+393471234567", "sender": "MyCompany", "body": "Hello from Agile Telecom SMS API!" } ) data = response.json() print(f"Message ID: {data['messageId']}") print(f"Parts: {data['results']['sms']['parts']}") ``` ``` ```javascript const response = await fetch( "https://lora-api.agiletelecom.com/api/message-server/sms/send", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, body: JSON.stringify({ destination: "+393471234567", sender: "MyCompany", body: "Hello from Agile Telecom SMS API!" }) } ); const data = await response.json(); console.log(`Message ID: ${data.messageId}`); console.log(`Parts: ${data.results.sms.parts}`); ``` ``` ```php $ch = curl_init("https://lora-api.agiletelecom.com/api/message-server/sms/send"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ], CURLOPT_POSTFIELDS => json_encode([ "destination" => "+393471234567", "sender" => "MyCompany", "body" => "Hello from Agile Telecom SMS API!" ]) ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); echo "Message ID: " . $data["messageId"] . "\n"; echo "Parts: " . $data["results"]["sms"]["parts"] . "\n"; ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "destination": "+393471234567", "sender": "MyCompany", "body": "Hello from Agile Telecom SMS API!" } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var payload = new { destination = "+393471234567", sender = "MyCompany", body = "Hello from Agile Telecom SMS API!" }; var content = new StringContent( System.Text.Json.JsonSerializer.Serialize(payload), System.Text.Encoding.UTF8, "application/json" ); var response = await client.PostAsync( "https://lora-api.agiletelecom.com/api/message-server/sms/send", content ); var result = await response.Content.ReadAsStringAsync(); Console.WriteLine(result); ``` ``` ```go package main "bytes" "encoding/json" "fmt" "net/http" "io" ) func main() { payload, _ := json.Marshal(map[string]string{ "destination": "+393471234567", "sender": "MyCompany", "body": "Hello from Agile Telecom SMS API!", }) req, _ := http.NewRequest("POST", "https://lora-api.agiletelecom.com/api/message-server/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ``` ```ruby require "net/http" require "json" require "uri" uri = URI("https://lora-api.agiletelecom.com/api/message-server/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = { destination: "+393471234567", sender: "MyCompany", body: "Hello from Agile Telecom SMS API!" }.to_json response = http.request(request) data = JSON.parse(response.body) puts "Message ID: #{data['messageId']}" puts "Parts: #{data['results']['sms']['parts']}" ``` ``` ### Response On success, the API returns **HTTP 202 Accepted**: ```json { "messageId": "9b3dfde1-7ee6-411f-8f56-6cb18106e0cc", "simulation": false, "results": { "sms": { "accepted": true, "unicode": false, "parts": 1 } } } ``` | Field | Description | |-------|-------------| | `messageId` | Unique identifier for tracking delivery status via webhooks | | `simulation` | `false` = real send, `true` = dry run (no SMS dispatched) | | `results.sms.accepted` | `true` means the message entered the delivery pipeline | | `results.sms.unicode` | Whether the message required Unicode encoding (emoji, CJK, Arabic, etc.) | | `results.sms.parts` | Number of SMS parts — messages over 160 chars (GSM) or 70 chars (Unicode) are split automatically | ## Simulation Mode Test your integration without sending a real SMS. The API validates everything — authentication, sender registration, message encoding — and returns a realistic response, but nothing reaches the recipient's phone. Set `"simulation": true` in the request body. All other parameters stay the same. ```json { "destination": "+393471234567", "sender": "MyCompany", "body": "This SMS will not be sent, it's only a simulation.", "simulation": true } ``` :::tip When to Use Simulation Use simulation mode during development and CI/CD pipeline testing. It validates your payload without consuming credits or reaching real phone numbers. ::: ## Scheduled Delivery Queue an SMS for future delivery. The message is stored server-side and dispatched at the specified date and time. Useful for appointment reminders, marketing campaigns, and time-sensitive notifications. Add `scheduledDate` in ISO 8601 format to the request body: ```json { "destination": "+393471234567", "sender": "MyCompany", "body": "Your appointment is tomorrow at 10:00.", "scheduledDate": "2025-10-30T18:50:00.000+0000", "enableNotification": true } ``` | Field | Description | |-------|-------------| | `scheduledDate` | Delivery date/time in ISO 8601 format with timezone offset | | `enableNotification` | Set to `true` to receive delivery callbacks for this message | :::warning Timezone Always include the timezone offset (e.g., `+0000` for UTC, `+0100` for CET). Without it, the scheduled time may be interpreted incorrectly. ::: ## Personalized Messages with Placeholders Insert dynamic values into your message text. Define placeholder tokens with curly braces in the body, then provide the replacement values in the `placeholders` object. Ideal for OTP codes, customer names, order numbers, and personalized notifications. ```json { "destination": "+393471234567", "sender": "MyCompany", "body": "Hello {name}, your code is {code}.", "placeholders": { "name": "Luigi", "code": "123456" } } ``` The recipient receives: **"Hello Luigi, your code is 123456."** :::info Placeholder Syntax Tokens are case-sensitive: `{Name}` and `{name}` are different placeholders. Make sure the keys in `placeholders` match the tokens in `body` exactly. ::: ## Custom Message ID By default, the system generates a UUID for each message. If you need to correlate messages with your internal records, pass your own `messageId`: ```json { "destination": "+393471234567", "sender": "MyCompany", "body": "Order #12345 has been shipped.", "messageId": "550e8400-e29b-41d4-a716-446655440000" } ``` The same ID will appear in delivery callbacks, allowing you to match delivery status to your records. ## UDH (User Data Header) For advanced use cases like WAP Push, Flash SMS, or custom concatenation, you can include raw UDH data in hexadecimal format: ```json { "destination": "+393471234567", "sender": "MyCompany", "body": "Message with custom UDH", "udhData": "050003CC0201" } ``` :::warning Advanced Feature UDH is intended for developers who need low-level control over SMS PDU headers. Most integrations do not need this — the API handles concatenation automatically. ::: ## Delivery Callbacks (Webhooks) When you configure a delivery URL in the [Agile Telecom portal](https://lora.agiletelecom.com/login), the system sends an HTTP POST to your endpoint each time a message status changes. This lets you track whether each SMS was actually delivered to the recipient's device. ### Webhook Payload Your endpoint receives a JSON payload like this: **Delivered successfully:** ```json { "channel": "SMS", "eventType": "DELIVERY", "messageId": "813d4183-8d15-4aa3-a820-8d16a435bf77", "destination": "+393401234567", "statusCode": 3, "description": "delivered", "eventDate": "2025-11-24T16:10:21Z", "price": 0.075, "numPart": 1, "totalParts": 2 } ``` **Undeliverable:** ```json { "channel": "SMS", "eventType": "DELIVERY", "messageId": "813d4183-8d15-4aa3-a820-8d16a435bf77", "destination": "+393401234567", "statusCode": 6, "description": "Undeliverable", "eventDate": "2025-11-24T16:10:21Z", "price": 0.075, "numPart": 1, "totalParts": 1 } ``` ### Webhook Fields | Field | Description | |-------|-------------| | `channel` | Always `"SMS"` for this API | | `eventType` | Always `"DELIVERY"` — fired when the carrier confirms the final status | | `messageId` | Matches the ID returned when you sent the message | | `destination` | Recipient's phone number | | `statusCode` | Numeric delivery status (see table below) | | `description` | Human-readable status label | | `eventDate` | ISO 8601 timestamp of the delivery event | | `price` | Cost charged for this message part | | `numPart` | Part number (for multipart SMS) | | `totalParts` | Total parts in the message | ### Delivery Status Codes | Code | Status | What It Means | |------|--------|---------------| | 1 | Accepted | Message entered the delivery pipeline, waiting to be sent | | 2 | Rejected | Validation failed before sending (invalid sender, restricted destination, etc.) | | 3 | Delivered | Confirmed delivered to the recipient's device | | 4 | Expired | Delivery window elapsed — the phone may have been off or unreachable | | 5 | Deleted | Message removed before delivery (by policy or on request) | | 6 | Undeliverable | Permanently failed — number doesn't exist, network error, etc. | ## Error Codes If the API rejects your request, it returns one of these error codes alongside an HTTP error status: | Code | Meaning | What to Check | |------|---------|---------------| | 8 | Server error | Retry after a brief delay; contact support if persistent | | 26 | Sender not authorized | Register the sender alias in the portal before using it | | 100 | IP not allowed | Your server IP must be whitelisted — contact support | ## SMS Encoding Reference | Encoding | Max Chars per Part | When Used | |----------|-------------------|-----------| | GSM 7-bit | 160 | Standard Latin characters, digits, common symbols | | Unicode (UCS-2) | 70 | Emoji, Chinese/Japanese/Korean, Arabic, Cyrillic, etc. | | Multipart GSM | 153 per part | GSM messages over 160 chars (7 chars per part reserved for UDH) | | Multipart Unicode | 67 per part | Unicode messages over 70 chars | The API detects the encoding automatically based on your message content. You don't need to specify it. :::info Sender ID Rules - **Alphanumeric sender**: max 11 characters (letters, digits, spaces). Must be pre-registered in the portal. - **Numeric sender**: max 16 digits. Useful for two-way SMS where recipients can reply. ::: ## Postman Collection The Postman Collection contains pre-configured requests for every example on this page. Import it and set your variables to start testing in seconds. 1. [Download the collection](https://drive.google.com/drive/folders/1H_uRui_3jdIXDutI5iqwr54D87yehFR8?usp=sharing) 2. Import into Postman via **File > Import** 3. Set `BASE_URL`, `API_KEY`, `DESTINATION`, and `SENDER` in the collection variables 4. Run any request — check the response for `messageId` and `accepted: true` :::tip Code Generation Postman can generate code snippets in 15+ languages. Open any request, click the **Code** icon (``) on the right, and select your language. ::: ## Support For technical assistance, contact [help@agiletelecom.com](mailto:help@agiletelecom.com). ## What's Next? - [SMS REST API (Legacy)](./sms-legacy.md) — older HTTP endpoint, compatible with existing integrations - [SMPP Protocol](./smpp.md) — binary protocol for high-throughput sending - [Inbound SMS](./inbound-sms.md) — receive messages on your dedicated number --- --- title: SMS REST API (Legacy) sidebar_label: SMS REST API (Legacy) sidebar_position: 2 description: Send SMS via REST API using HTTP POST for legacy Agile Telecom customers with support for concatenated messages, Unicode, and scheduled sending. --- ``` Send SMS to your customers with a simple, reliable REST API. This legacy endpoint supports all the messaging features you need—from basic texts to scheduled sends, concatenated messages, and bulk broadcasting. :::warning Security Update Starting from January 15th, 2026, HTTP connections are accepted only over TLS (HTTPS). Non-TLS connections will be rejected. GET requests and form-encoded POST requests will no longer be accepted—only POST with JSON payload over HTTPS is supported. ::: ## Base URLs Choose the endpoint based on your account type: **Legacy customers:** ``` https://secure.agiletelecom.com/services/ ``` **New customers:** ``` https://http-api.agiletelecom.com/services/ ``` ## Authentication Add one of these authentication methods to your request headers: ### Basic Authentication ``` Authorization: Basic ``` ### API Key (Recommended) ``` X-Api-Key: YOUR_API_KEY ``` ## Send SMS Send SMS messages to one or multiple recipients in a single API call. **Endpoint** ``` POST /sms/send ``` **Required Headers** ``` Content-Type: application/json ``` ### Request Body ```json { "globalId": "string", "maxIdLen": 64, "enableConcatenated": true, "enableUnicode": true, "enableDelivery": true, "simulation": false, "messages": [ { "ids": ["string"], "destinations": ["+393351234567"], "sender": "string", "body": "string", "hexBody": false, "udhData": "string", "scheduling": "2025-12-08 06:00:00.002+0200" } ] } ``` ### Request Parameters | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | globalId | string | No | -- | Unique identifier for this send request, useful for tracking and correlating responses | | maxIdLen | integer | No | 64 | Maximum length (characters) of message IDs | | enableConcatenated | boolean | No | true | Automatically split long messages into multiple SMS parts | | enableUnicode | boolean | No | true | Support special characters and non-ASCII text | | enableDelivery | boolean | No | true | Receive delivery notifications via webhook | | simulation | boolean | No | false | Test your request without actually sending messages | | messages | array | Yes | -- | Array of message objects to send | ### Message Parameters | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | destinations | array | Yes | -- | Phone numbers in international format (include country code) | | ids | array | No | -- | Unique identifiers for tracking each message | | sender | string | Yes | -- | Sender ID (alphanumeric max 11 chars, or numeric max 16 digits) | | body | string | Yes | -- | Message content (text or HEX if hexBody=true) | | hexBody | boolean | No | false | Set true if body is HEX-encoded | | udhData | string | No | -- | User Data Header for special message formatting | | scheduling | string | No | -- | Delivery time in format "yyyy-MM-dd HH:mm:ss.SSSZ" | ## Check Account Credit Verify your remaining account balance before sending. **Endpoint** ``` GET /sms/credit ``` **Success Response** ```json { "credit": 10.324 } ``` ## Response Formats ### Success Response (2XX) When your request succeeds, you'll receive: ```json { "globalId": "f9b865ef-5ce3-4e44-b65c-615fd71bbd09", "processedMessages": 2, "processedSmsParts": 4, "credit": 10.324 } ``` | Field | Type | Description | |-------|------|-------------| | globalId | string | Echo of your global ID from the request | | processedMessages | integer | Number of message objects accepted | | processedSmsParts | integer | Total SMS parts to be sent (higher if concatenation occurs) | | credit | double | Your remaining credit in EUR | ### Client Errors (4XX) Validation or authentication issues: ```json { "status": "fail", "data": { "body": "Missing message body(ies)", "number": "Wrong destination number(s)" }, "code": 6 } ``` | Field | Type | Description | |-------|------|-------------| | status | string | Always "fail" for 4XX responses | | data | object/string | Details about which fields failed validation | | code | integer | Error code (see table below) | ### Server Errors (5XX) Service unavailability or timeouts: ```json { "status": "error", "message": "Number check service unavailable", "code": 9 } ``` | Field | Type | Description | |-------|------|-------------| | status | string | Always "error" for 5XX responses | | message | string | Human-readable error description | | code | integer | Error code (see table below) | ## Error Codes | Code | Description | |------|-------------| | 1 | Wrong credentials | | 2 | Insufficient credit | | 3 | Destination not allowed | | 4 | Unknown error | | 5 | Destination number too short | | 6 | Destination number too long | | 7 | Invalid destination number | | 8 | Server error | | 9 | Service timeout | | 10 | Wrong global ID length | | 26 | Sender alias not allowed | | 100 | Source IP not allowed | ## Code Examples ### Simple SMS Send a basic text message to a single recipient. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "messages": [ { "destinations": ["+393471234567"], "sender": "MyApp", "body": "Hello! This is your test message from Agile Telecom." } ] }' ``` ``` ```python url = "https://secure.agiletelecom.com/services/sms/send" headers = { "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" } payload = { "messages": [ { "destinations": ["+393471234567"], "sender": "MyApp", "body": "Hello! This is your test message from Agile Telecom." } ] } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ messages: [ { destinations: ["+393471234567"], sender: "MyApp", body: "Hello! This is your test message from Agile Telecom." } ] }); const options = { hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }; const req = https.request(options, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php [ [ "destinations" => ["+393471234567"], "sender" => "MyApp", "body" => "Hello! This is your test message from Agile Telecom." ] ] ]); $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: {$apiKey}" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo json_encode(json_decode($response), JSON_PRETTY_PRINT); curl_close($ch); ?> ``` ``` ```java public class SendSMS { public static void main(String[] args) throws Exception { String url = "https://secure.agiletelecom.com/services/sms/send"; String apiKey = "YOUR_API_KEY"; String jsonPayload = """ { "messages": [ { "destinations": ["+393471234567"], "sender": "MyApp", "body": "Hello! This is your test message from Agile Telecom." } ] } """; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .header("X-Api-Key", apiKey) .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; class Program { static async Task Main() { string url = "https://secure.agiletelecom.com/services/sms/send"; string apiKey = "YOUR_API_KEY"; string jsonPayload = @"{ ""messages"": [ { ""destinations"": [""+393471234567""], ""sender"": ""MyApp"", ""body"": ""Hello! This is your test message from Agile Telecom."" } ] }"; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, content); var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseContent); } } } ``` ``` ```go package main "bytes" "fmt" "io" "net/http" ) func main() { url := "https://secure.agiletelecom.com/services/sms/send" apiKey := "YOUR_API_KEY" payload := []byte(`{ "messages": [ { "destinations": ["+393471234567"], "sender": "MyApp", "body": "Hello! This is your test message from Agile Telecom." } ] }`) req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", apiKey) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' require 'uri' url = URI("https://secure.agiletelecom.com/services/sms/send") api_key = "YOUR_API_KEY" payload = { messages: [ { destinations: ["+393471234567"], sender: "MyApp", body: "Hello! This is your test message from Agile Telecom." } ] }.to_json http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = api_key request.body = payload response = http.request(request) puts response.body ``` ``` ### SMS with Custom Message ID Track individual messages with unique IDs for delivery reporting. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "messages": [ { "ids": ["msg-001-20260408"], "destinations": ["+393471234567"], "sender": "MyApp", "body": "Order confirmed! Your purchase reference is ORD-2026-4521." } ] }' ``` ``` ```python response = requests.post( "https://secure.agiletelecom.com/services/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "messages": [ { "ids": ["msg-001-20260408"], "destinations": ["+393471234567"], "sender": "MyApp", "body": "Order confirmed! Your purchase reference is ORD-2026-4521." } ] } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ messages: [ { ids: ["msg-001-20260408"], destinations: ["+393471234567"], sender: "MyApp", body: "Order confirmed! Your purchase reference is ORD-2026-4521." } ] }); const req = https.request({ hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php [ [ "ids" => ["msg-001-20260408"], "destinations" => ["+393471234567"], "sender" => "MyApp", "body" => "Order confirmed! Your purchase reference is ORD-2026-4521." ] ] ]); $ch = curl_init("https://secure.agiletelecom.com/services/sms/send"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); echo json_decode($response, true); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "messages": [ { "ids": ["msg-001-20260408"], "destinations": ["+393471234567"], "sender": "MyApp", "body": "Order confirmed! Your purchase reference is ORD-2026-4521." } ] } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var json = @"{ ""messages"": [ { ""ids"": [""msg-001-20260408""], ""destinations"": [""+393471234567""], ""sender"": ""MyApp"", ""body"": ""Order confirmed! Your purchase reference is ORD-2026-4521."" } ] }"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://secure.agiletelecom.com/services/sms/send", content ); var result = await response.Content.ReadAsStringAsync(); Console.WriteLine(result); } ``` ``` ```go package main "bytes" "io" "net/http" ) func main() { payload := []byte(`{ "messages": [ { "ids": ["msg-001-20260408"], "destinations": ["+393471234567"], "sender": "MyApp", "body": "Order confirmed! Your purchase reference is ORD-2026-4521." } ] }`) req, _ := http.NewRequest("POST", "https://secure.agiletelecom.com/services/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' payload = { messages: [ { ids: ["msg-001-20260408"], destinations: ["+393471234567"], sender: "MyApp", body: "Order confirmed! Your purchase reference is ORD-2026-4521." } ] }.to_json uri = URI("https://secure.agiletelecom.com/services/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = payload response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Long Message (Concatenated SMS) Automatically split long messages into multiple SMS parts. Requires `enableConcatenated: true`. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "enableConcatenated": true, "messages": [ { "destinations": ["+393471234567"], "sender": "Newsletter", "body": "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] }' ``` ``` ```python response = requests.post( "https://secure.agiletelecom.com/services/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "enableConcatenated": True, "messages": [ { "destinations": ["+393471234567"], "sender": "Newsletter", "body": "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ enableConcatenated: true, messages: [ { destinations: ["+393471234567"], sender: "Newsletter", body: "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] }); const req = https.request({ hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php true, "messages" => [ [ "destinations" => ["+393471234567"], "sender" => "Newsletter", "body" => "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." ] ] ]); $ch = curl_init("https://secure.agiletelecom.com/services/sms/send"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); print_r(json_decode($response, true)); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "enableConcatenated": true, "messages": [ { "destinations": ["+393471234567"], "sender": "Newsletter", "body": "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var json = @"{ ""enableConcatenated"": true, ""messages"": [ { ""destinations"": [""+393471234567""], ""sender"": ""Newsletter"", ""body"": ""Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours."" } ] }"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://secure.agiletelecom.com/services/sms/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); } ``` ``` ```go package main "bytes" "io" "net/http" ) func main() { payload := []byte(`{ "enableConcatenated": true, "messages": [ { "destinations": ["+393471234567"], "sender": "Newsletter", "body": "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] }`) req, _ := http.NewRequest("POST", "https://secure.agiletelecom.com/services/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' payload = { enableConcatenated: true, messages: [ { destinations: ["+393471234567"], sender: "Newsletter", body: "Stay healthy this summer with our top wellness tips. Drink plenty of water throughout the day to stay hydrated. Always wear broad-spectrum sunscreen (SPF 30+) to protect your skin from UV damage. Include fresh fruits and vegetables in every meal. Exercise for at least 30 minutes during cooler morning or evening hours." } ] }.to_json uri = URI("https://secure.agiletelecom.com/services/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = payload response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Unicode Message Send messages with special characters, accents, and non-ASCII text. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "enableUnicode": true, "messages": [ { "destinations": ["+393471234567"], "sender": "WeatherAlert", "body": "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] }' ``` ``` ```python response = requests.post( "https://secure.agiletelecom.com/services/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "enableUnicode": True, "messages": [ { "destinations": ["+393471234567"], "sender": "WeatherAlert", "body": "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ enableUnicode: true, messages: [ { destinations: ["+393471234567"], sender: "WeatherAlert", body: "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] }); const req = https.request({ hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php true, "messages" => [ [ "destinations" => ["+393471234567"], "sender" => "WeatherAlert", "body" => "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." ] ] ]); $ch = curl_init("https://secure.agiletelecom.com/services/sms/send"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); echo json_encode(json_decode(curl_exec($ch)), JSON_PRETTY_PRINT); curl_close($ch); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "enableUnicode": true, "messages": [ { "destinations": ["+393471234567"], "sender": "WeatherAlert", "body": "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var json = @"{ ""enableUnicode"": true, ""messages"": [ { ""destinations"": [""+393471234567""], ""sender"": ""WeatherAlert"", ""body"": ""ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00."" } ] }"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://secure.agiletelecom.com/services/sms/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); } ``` ``` ```go package main "bytes" "io" "net/http" ) func main() { payload := []byte(`{ "enableUnicode": true, "messages": [ { "destinations": ["+393471234567"], "sender": "WeatherAlert", "body": "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] }`) req, _ := http.NewRequest("POST", "https://secure.agiletelecom.com/services/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' payload = { enableUnicode: true, messages: [ { destinations: ["+393471234567"], sender: "WeatherAlert", body: "ATTENZIONE: Domani temperature fino a 38°C. Rimani idratato e stai al riparo dalle 12:00 alle 17:00." } ] }.to_json uri = URI("https://secure.agiletelecom.com/services/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = payload response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Scheduled SMS Send a message at a specific time in the future. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "messages": [ { "destinations": ["+393471234567"], "sender": "ReminderBot", "body": "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", "scheduling": "2026-04-09 19:30:00.000+0200" } ] }' ``` ``` ```python response = requests.post( "https://secure.agiletelecom.com/services/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "messages": [ { "destinations": ["+393471234567"], "sender": "ReminderBot", "body": "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", "scheduling": "2026-04-09 19:30:00.000+0200" } ] } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ messages: [ { destinations: ["+393471234567"], sender: "ReminderBot", body: "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", scheduling: "2026-04-09 19:30:00.000+0200" } ] }); const req = https.request({ hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php [ [ "destinations" => ["+393471234567"], "sender" => "ReminderBot", "body" => "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", "scheduling" => "2026-04-09 19:30:00.000+0200" ] ] ]); $ch = curl_init("https://secure.agiletelecom.com/services/sms/send"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); echo json_encode(json_decode(curl_exec($ch)), JSON_PRETTY_PRINT); curl_close($ch); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "messages": [ { "destinations": ["+393471234567"], "sender": "ReminderBot", "body": "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", "scheduling": "2026-04-09 19:30:00.000+0200" } ] } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var json = @"{ ""messages"": [ { ""destinations"": [""+393471234567""], ""sender"": ""ReminderBot"", ""body"": ""Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early."", ""scheduling"": ""2026-04-09 19:30:00.000+0200"" } ] }"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://secure.agiletelecom.com/services/sms/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); } ``` ``` ```go package main "bytes" "io" "net/http" ) func main() { payload := []byte(`{ "messages": [ { "destinations": ["+393471234567"], "sender": "ReminderBot", "body": "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", "scheduling": "2026-04-09 19:30:00.000+0200" } ] }`) req, _ := http.NewRequest("POST", "https://secure.agiletelecom.com/services/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' payload = { messages: [ { destinations: ["+393471234567"], sender: "ReminderBot", body: "Reminder: Your appointment is tomorrow at 10:00 AM. Please arrive 10 minutes early.", scheduling: "2026-04-09 19:30:00.000+0200" } ] }.to_json uri = URI("https://secure.agiletelecom.com/services/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = payload response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Bulk SMS to Multiple Recipients Send the same message to multiple phone numbers in one request. ``` ```bash curl -X POST https://secure.agiletelecom.com/services/sms/send \ -H "Content-Type: application/json" \ -H "X-Api-Key: YOUR_API_KEY" \ -d '{ "messages": [ { "ids": ["bulk-promo-001", "bulk-promo-002"], "destinations": ["+393471234567", "+393471234568"], "sender": "PromoShop", "body": "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] }' ``` ``` ```python response = requests.post( "https://secure.agiletelecom.com/services/sms/send", headers={ "Content-Type": "application/json", "X-Api-Key": "YOUR_API_KEY" }, json={ "messages": [ { "ids": ["bulk-promo-001", "bulk-promo-002"], "destinations": ["+393471234567", "+393471234568"], "sender": "PromoShop", "body": "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const data = JSON.stringify({ messages: [ { ids: ["bulk-promo-001", "bulk-promo-002"], destinations: ["+393471234567", "+393471234568"], sender: "PromoShop", body: "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] }); const req = https.request({ hostname: 'secure.agiletelecom.com', path: '/services/sms/send', method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'YOUR_API_KEY', 'Content-Length': data.length } }, (res) => { let responseData = ''; res.on('data', (chunk) => { responseData += chunk; }); res.on('end', () => { console.log(JSON.parse(responseData)); }); }); req.write(data); req.end(); ``` ``` ```php [ [ "ids" => ["bulk-promo-001", "bulk-promo-002"], "destinations" => ["+393471234567", "+393471234568"], "sender" => "PromoShop", "body" => "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." ] ] ]); $ch = curl_init("https://secure.agiletelecom.com/services/sms/send"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "X-Api-Key: YOUR_API_KEY" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); echo json_encode(json_decode(curl_exec($ch)), JSON_PRETTY_PRINT); curl_close($ch); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); String json = """ { "messages": [ { "ids": ["bulk-promo-001", "bulk-promo-002"], "destinations": ["+393471234567", "+393471234568"], "sender": "PromoShop", "body": "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/send")) .header("Content-Type", "application/json") .header("X-Api-Key", "YOUR_API_KEY") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var json = @"{ ""messages"": [ { ""ids"": [""bulk-promo-001"", ""bulk-promo-002""], ""destinations"": [""+393471234567"", ""+393471234568""], ""sender"": ""PromoShop"", ""body"": ""Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight."" } ] }"; var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( "https://secure.agiletelecom.com/services/sms/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); } ``` ``` ```go package main "bytes" "io" "net/http" ) func main() { payload := []byte(`{ "messages": [ { "ids": ["bulk-promo-001", "bulk-promo-002"], "destinations": ["+393471234567", "+393471234568"], "sender": "PromoShop", "body": "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] }`) req, _ := http.NewRequest("POST", "https://secure.agiletelecom.com/services/sms/send", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' payload = { messages: [ { ids: ["bulk-promo-001", "bulk-promo-002"], destinations: ["+393471234567", "+393471234568"], sender: "PromoShop", body: "Limited-time offer: Buy 2 get 1 free on all summer items! Valid until Sunday midnight." } ] }.to_json uri = URI("https://secure.agiletelecom.com/services/sms/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request["Content-Type"] = "application/json" request["X-Api-Key"] = "YOUR_API_KEY" request.body = payload response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Check Account Credit Verify your current credit balance before sending campaigns. ``` ```bash curl -X GET https://secure.agiletelecom.com/services/sms/credit \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python response = requests.get( "https://secure.agiletelecom.com/services/sms/credit", headers={ "X-Api-Key": "YOUR_API_KEY" } ) print(response.json()) ``` ``` ```javascript const https = require('https'); const options = { hostname: 'secure.agiletelecom.com', path: '/services/sms/credit', method: 'GET', headers: { 'X-Api-Key': 'YOUR_API_KEY' } }; https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log(JSON.parse(data)); }); }).end(); ``` ``` ```php ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/credit")) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var response = await client.GetAsync("https://secure.agiletelecom.com/services/sms/credit"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); } ``` ``` ```go package main "fmt" "io" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://secure.agiletelecom.com/services/sms/credit", nil) req.Header.Set("X-Api-Key", "YOUR_API_KEY") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ``` ```ruby require 'net/https' require 'json' uri = URI("https://secure.agiletelecom.com/services/sms/credit") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri.path) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts JSON.parse(response.body) ``` ``` ## Delivery Notifications When you enable delivery notifications, AgileTelecom will post the delivery status of each SMS to your webhook endpoint (configured in your account settings). This lets you track whether messages were successfully delivered, rejected, or expired. ### Webhook Payload Format ```json { "messageId": "msg-001-20260408", "destination": "+393471234567", "statusCode": 3, "description": "Delivered", "doneDate": "2026-04-08 14:35:22.045+0200", "concatTotal": 1, "concatProgressive": 1, "lcrOperator": "22210", "realOperator": "22201", "price": 0.005 } ``` ### Report Fields | Field | Type | Description | |-------|------|-------------| | messageId | string | Your message ID from the send request | | destination | string | Recipient's phone number | | statusCode | integer | Status code (see table below) | | description | string | Human-readable status (Delivered, Rejected, etc.) | | doneDate | string | When the delivery status was determined | | concatTotal | integer | Total parts if message was concatenated | | concatProgressive | integer | Which part of a concatenated message this is | | lcrOperator | string | Network operator code (if available) | | realOperator | string | Actual carrier handling the number | | price | double | Credit cost for this SMS | ### Delivery Status Codes | Code | Description | Meaning | |------|-------------|---------| | 1 | Accepted | Operator has accepted the message | | 2 | Rejected | Operator rejected the message | | 3 | Delivered | Message successfully delivered to phone | | 4 | Expired | Message was not delivered before expiry | | 5 | Deleted | Message was deleted | | 6 | Undeliverable | Operator unable to deliver | ## Best Practices :::tip Message Tracking Always include unique IDs in the `ids` array when sending messages—this makes webhook delivery reports much easier to match against your records. ::: :::info Phone Number Format Use international format with country code. Examples: +39 for Italy, +44 for UK, +1 for USA, +33 for France. ::: :::info SMS Length Limits - Standard (GSM 7-bit alphabet): 160 characters per part - Unicode (special characters): 70 characters per part - Messages longer than these limits are automatically split into multiple parts (concatenated SMS) if `enableConcatenated` is `true` ::: :::info Sender ID Rules - **Alphanumeric**: Maximum 11 characters (e.g., "MyCompany", "Support") - **Numeric**: Maximum 16 digits (e.g., "1234567890123456") - Check error code 26 if your sender ID is rejected ::: :::warning Rate Limits Contact AgileTelecom support to confirm your account's rate limits (messages per second/minute). Exceeding these limits may result in request queueing or rejection. ::: :::note Encoding By default, messages are sent as UTF-8 text. For HEX-encoded bodies, set `hexBody: true` in your request. ::: ## Test with Postman We provide pre-configured Postman collections to speed up your testing: ### Download Collections - [Legacy Customers Collection](https://drive.google.com/file/d/13AwN29ZxL5VE2fc_LoutKIT59Woi3-V7/view?usp=drive_link) - [New Customers Collection](https://drive.google.com/file/d/1PCwew44n7ADQZrHT4xKR16qYPoetRG-u/view?usp=sharing) ### How to Import 1. Open Postman 2. Click **Import** (top left corner) 3. Upload the downloaded JSON file 4. Confirm the import ### Set Your Credentials 1. In Postman, open the collection in the left sidebar 2. Navigate to the **Variables** tab 3. Update the "Current Value" for: - `username`: Your Agile Telecom username (if using Basic Auth) - `password`: Your password (if using Basic Auth) - `api-key`: Your API key (if using API Key auth) - `enable_apikey`: Set to `true` for API Key, `false` for Basic Auth 4. Click **Save** :::note Test Data The collection includes sample requests and placeholder credentials. Replace them with your real credentials before sending actual messages. ::: ### Generate Code Snippets Generate integration code directly from Postman: 1. Open any request in the collection 2. Click the **** code icon (right side) 3. Select your language (cURL, Python, Node.js, PHP, Java, C#, Go, Ruby) 4. Copy the generated code into your application ## Troubleshooting **Error 1 (Wrong credentials)**: Verify your API key or username/password are correct and enabled in your account. **Error 2 (Insufficient credit)**: Your account balance is too low. Check credit with the `/sms/credit` endpoint and top up if needed. **Error 26 (Sender alias not allowed)**: The sender ID you used is not approved. Configure allowed sender IDs in your account settings. **Error 100 (Source IP not allowed)**: Your server's IP address is not whitelisted. Update IP whitelist in your account settings. ## Need Help? For technical support or billing questions, reach out to AgileTelecom: **Email**: [help@agiletelecom.com](mailto:help@agiletelecom.com) ## Next Steps Explore related APIs and protocols: - [SMS REST API (Universal)](sms-universal.md) - Modern API endpoint with additional features - [SMPP Protocol](smpp.md) - Binary protocol for high-volume senders - [Inbound SMS](inbound-sms.md) - Receive and process incoming messages --- --- title: SMPP Protocol sidebar_label: SMPP Protocol sidebar_position: 3 description: Connect via SMPP to send and receive SMS using TCP/IP connections --- SMPP (Short Message Peer-to-Peer Protocol) is a binary protocol designed for reliable, persistent connections to send SMS at scale with delivery tracking. Choose SMPP when you need persistent connections for high-volume messaging, real-time delivery notifications, or integration with existing telecom infrastructure. ## Connection Modes Agile Telecom's SMPP server supports three connection modes. Choose the one that fits your use case: - **Transmitter (TX)** – Send messages only. Use this if you only originate SMS and don't need delivery reports. - **Receiver (RX)** – Receive delivery notifications only. Use this for dedicated notification monitoring from TX connections elsewhere. - **Transceiver (TRX)** – Send messages and receive notifications over the same connection. Ideal for bi-directional messaging with fewer connection slots. By default, your account allows up to 4 active connections—combine modes as needed (e.g., 4 TX + 4 RX, or 4 TRX). Contact support if you need additional capacity. ## Connection Details | Parameter | Value | |-----------|-------| | HOST | smpp.agiletelecom.com | | PORT | 2776 (TLS) | | SYSTEM ID | Your account username | | PASSWORD | Your account password | | MODE | TX, RX, or TRX | | Login-AddrRange | Leave empty | | Login-SystemType | Leave empty | | Login-TON | 1 | | Login-NPI | 1 | | Login-DCS | 0 | :::tip Keep Your Connection Alive Send an ENQUIRE_LINK packet at least every 30 seconds. If no response is received within 5 minutes, the connection will be closed. Most SMPP libraries handle this automatically. ::: ## Code Examples Install the `smpplib` library: ```bash pip install smpplib ``` Connect and send an SMS: ```python # Create a client client = smpplib.client.Client("smpp.agiletelecom.com", 2776, allow_unknown_opt_params=True) # Connect with TLS client.connect() # Bind as Transceiver (TRX mode) client.bind( smpplib.consts.SMPP_BIND_TX, system_id="YOUR_USERNAME", password="YOUR_PASSWORD", ) # Send SMS parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts("Hello World!") for part in parts: pdu = client.send_message( source_addr_ton=smpplib.consts.SMPP_TON_INTL, source_addr="12345", dest_addr_ton=smpplib.consts.SMPP_TON_INTL, destination_addr="+391234567890", short_message=part, ) print(f"Message ID: {pdu.message_id}") # Keep connection alive while True: client.listen() time.sleep(1) client.unbind() client.disconnect() ``` Install the `smpp` library: ```bash npm install smpp ``` Connect and send an SMS: ```javascript const smpp = require("smpp"); const client = new smpp.Session({ host: "smpp.agiletelecom.com", port: 2776, tls: true, logger: "bunyan", }); client.on("error", (err) => { console.error("Client error:", err); }); client.on("connect", () => { console.log("Connected to SMPP server"); // Bind as Transceiver client.bind_transceiver( { system_id: "YOUR_USERNAME", password: "YOUR_PASSWORD", }, (pdu) => { if (pdu.command_status === 0) { console.log("Successfully bound"); // Send SMS client.submit_sm( { source_addr_ton: 1, source_addr: "12345", dest_addr_ton: 1, destination_addr: "+391234567890", short_message: "Hello World!", }, (pdu) => { console.log(`Message ID: ${pdu.message_id}`); client.unbind(); client.close(); } ); } } ); }); client.connect(); ``` Add dependency (using Maven): ```xml com.cloudhopper ch-smpp 6.0.0 ``` Connect and send: ```java SmppSessionConfiguration config = new SmppSessionConfiguration(); config.setWindowSize(1); config.setName("Agile Telecom"); config.setType(SmppSessionConfiguration.TYPE_TRANSCEIVER); config.setHost("smpp.agiletelecom.com"); config.setPort(2776); config.setConnectTimeout(5000); config.setBindTimeout(5000); config.setSystemId("YOUR_USERNAME"); config.setPassword("YOUR_PASSWORD"); config.setUsingSSL(true); DefaultSmppSessionFactory factory = new DefaultSmppSessionFactory(); SmppSession session = factory.createSmppSession(config); session.rebind(); SubmitSm pdu = new SubmitSm(); pdu.setSourceAddress("12345"); pdu.setDestinationAddress("+391234567890"); pdu.setShortMessage("Hello World!".getBytes()); PduAsyncResponse asyncResponse = session.submitSmAsync(pdu); SubmitSmResp response = (SubmitSmResp) asyncResponse.getResponse(); System.out.println("Message ID: " + response.getMessageId()); session.unbindAndClose(); ``` Install the `php-smpp` library: ```bash composer require onurb/php-smpp ``` Connect and send: ```php 'smpp.agiletelecom.com', 'port' => 2776, 'use_ssl' => true, 'system_id' => 'YOUR_USERNAME', 'password' => 'YOUR_PASSWORD', ]); // Connect and bind $client->connect(); $client->bindTransmitter(); // Send SMS $pdu = new SubmitSM(); $pdu->source_addr = '12345'; $pdu->destination_addr = '+391234567890'; $pdu->short_message = 'Hello World!'; $response = $client->submit($pdu); echo "Message ID: " . $response->message_id . "\n"; $client->close(); ?> ``` Install the SMPP library: ```bash go get github.com/fiorix/go-smpp/smpp ``` Connect and send: ```go package main "fmt" "log" "github.com/fiorix/go-smpp/smpp" ) func main() { conn := &smpp.Conn{ Addr: "smpp.agiletelecom.com:2776", User: "YOUR_USERNAME", Passwd: "YOUR_PASSWORD", TLS: true, } session, err := conn.Session() if err != nil { log.Fatal(err) } defer session.Close() // Send SMS pdu, err := session.Submit(&smpp.SubmitSM{ SourceAddr: "12345", DestinationAddr: "+391234567890", ShortMessage: []byte("Hello World!"), }) if err != nil { log.Fatal(err) } fmt.Println("Message ID:", pdu.(*smpp.SubmitSMResp).MessageID) } ``` Install NuGet package: ```bash dotnet add package AleaIOT.SMPP ``` Connect and send: ```csharp using SMPP; using SMPP.PDU; class Program { static async Task Main() { var client = new SMPPClient(); await client.ConnectAsync( "smpp.agiletelecom.com", 2776, useTLS: true ); var bindResp = await client.BindAsync( systemId: "YOUR_USERNAME", password: "YOUR_PASSWORD", bindMode: BindMode.Transceiver ); if (bindResp.Status == CommandStatus.ESME_ROK) { var submitResp = await client.SubmitSmAsync( sourceAddr: "12345", destAddr: "+391234567890", message: "Hello World!" ); Console.WriteLine($"Message ID: {submitResp.MessageID}"); } await client.DisconnectAsync(); } } ``` Install the gem: ```bash gem install ruby-smpp ``` Connect and send: ```ruby require 'ruby-smpp' # Create connection gateway = SMPP.connect( host: 'smpp.agiletelecom.com', port: 2776, ssl: true ) # Bind as Transceiver gateway.bind_transceiver( system_id: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' ) do |pdu| if pdu.command_status == 0 puts "Bound successfully" # Send SMS gateway.send_message( source_addr: '12345', destination_addr: '+391234567890', short_message: 'Hello World!' ) do |resp| puts "Message ID: #{resp.message_id}" end end end gateway.close ``` ``` ## Handling Errors When your connection fails or a message doesn't submit, check these error codes to understand what happened and how to fix it. ### Bind & Authentication Errors | Error Code | Hex | Description | |-----------|-----|-------------| | 4 | 0x04 | **Bind not yet completed** — Send bind request first before submitting messages | | 5 | 0x05 | **User already bound** — You've reached your connection limit (default: 4). Close unused connections or request higher limits | | 13 | 0x0E | **Invalid credentials** — Check your SYSTEM ID and password | | 8 | 0x08 | **System error** — Try again after a few seconds | | 10 | 0x0A | **Invalid source address** — The sender ID must be numeric (max 16 chars) or alphanumeric (max 11 chars). See sender ID rules below | | 11 | 0x0B | **Invalid destination address** — Use a valid international phone number (e.g., +391234567890) | | 21 | 0x15 | **Invalid SYSTEM_TYPE** — Leave this field empty | | 88 | 0x58 | **Throughput limit exceeded** — You're sending too fast. Slow down and implement backoff retry logic | | 259 | 0x103 | **Insufficient credit** — Your account balance is too low. Top up your credit | | 1026 | 0x402 | **Sender ID not allowed** — The sender ID is blocked or not approved. Check your registered aliases or request approval | | 1 | 0x01 | **Invalid message length** — Message is too long. SMS limited to 160 characters (or 70 for unicode) | | 69 | 0x45 | **Text decoding failed** — Message encoding issue. Check character set and try again | | 97 | 0x61 | **Invalid scheduled delivery time** — Format must be YYYYMMDDHHMMSS | | 98 | 0x62 | **Invalid validity period** — Expiry time must be after current time | ## Delivery Status Error Codes When a message is delivered or fails, you'll receive a DELIVER_SM notification with a status code. These codes indicate what happened on the carrier's network after you submitted your message. ### Routing & ESME Errors | Code | Category | Description | |------|----------|-------------| | 101 | ROUTING ERROR | No route found | | 102 | ESME ERROR | ESME error | ### HLR Errors | Code | Category | Description | |------|----------|-------------| | 201 | HLR ERROR | Absent subscriber | | 202 | HLR ERROR | Facility not supported | | 203 | HLR ERROR | System failure | | 204 | HLR ERROR | Unexpected data value | | 205 | HLR ERROR | Data missing | | 206 | HLR ERROR | Memory capacity exceeded | | 207 | HLR ERROR | Mobile subscriber not reachable | | 208 | HLR ERROR | Reject | | 209 | HLR ERROR | Local Cancel | | 210 | HLR ERROR | Abort | | 211 | HLR ERROR | Exception (internal) | | 212 | HLR ERROR | Unknown subscriber | | 213 | HLR ERROR | Teleservice not provisioned | | 214 | HLR ERROR | Call barred | | 215 | HLR ERROR | Roaming not allowed | | 216 | HLR ERROR | Bearer service not provisioned | | 350 | HLR ERROR | Unknown error | ### MSC Errors | Code | Category | Description | |------|----------|-------------| | 401 | MSC ERROR | Unidentified subscriber | | 402 | MSC ERROR | Absent subscriber, IMSI detached | | 403 | MSC ERROR | Absent subscriber, no page response | | 404 | MSC ERROR | Subscriber busy for MT SMS | | 405 | MSC ERROR | Facility not supported | | 406 | MSC ERROR | Illegal subscriber | | 407 | MSC ERROR | Illegal equipment | | 408 | MSC ERROR | System failure | | 409 | MSC ERROR | Unexpected data value | | 410 | MSC ERROR | Data missing | | 411 | MSC ERROR | Memory capacity exceeded | | 412 | MSC ERROR | Equipment protocol error | | 413 | MSC ERROR | Device not short message equipped | | 414 | MSC ERROR | Reject | | 415 | MSC ERROR | Local Cancel | | 416 | MSC ERROR | Abort | | 417 | MSC ERROR | Exception (internal) | | 418 | MSC ERROR | SM delivery failure | | 419 | MSC ERROR | Temporary mobile error | | 550 | MSC ERROR | Unknown error | ### SMSC Errors | Code | Category | Description | |------|----------|-------------| | 615 | SMSC ERROR | Network Timeout | | 616 | SMSC ERROR | Routing error | | 617 | SMSC ERROR | Mobile device error | | 618 | SMSC ERROR | Invalid text or charset | ### Other Errors | Code | Category | Description | |------|----------|-------------| | 806 | SCREENING ERROR | Destination blocked | | 901 | ESME EXTERNAL ERROR | Invalid destination address | | 902 | ESME EXTERNAL ERROR | Invalid source address | | 903 | ESME EXTERNAL ERROR | Message queue full | | 904 | ESME EXTERNAL ERROR | Invalid parameter | ## Sender ID Requirements Choose your sender ID based on the message type and recipient expectation. | Type | Max Length | Example | When to Use | |------|-----------|---------|------------| | **Numeric (International)** | 16 characters | +391234567890123 | Transactional SMS from services (receipts, alerts) | | **Alphanumeric** | 11 characters | COMPANY ABC | Marketing, branding (must be pre-approved) | Numeric sender IDs are displayed as-is. Alphanumeric IDs require approval—contact support with your desired sender name. *Limits comply with AGCOM regulation no. 42/13/CIR (effective October 16, 2013).* ## Message Tracking ### Message IDs in Notifications The message_id returned in SUBMIT_SM_RESP (your receipt of the submit) and used in DELIVER_SM notifications uses a UUID format: ``` AGILE-XXXXXXXX-XXXXXX-XXXXXX-XXXXXX ``` Store this ID to match delivery reports back to your outbound messages. If your system can't handle UUIDs, contact support to request an alternative format. ### Delivery Timestamps Delivery notifications include the timestamp when the message was delivered to the handset, in this format: ``` yyMMddHHmmss ``` For example: `260408143052` = April 8, 2026 at 14:30:52 UTC. Note that the year is 2-digit. ## Security: IP Binding By default, the SMPP server accepts connections from any IP address. For higher security, you can restrict access to specific IPs or subnets—useful for production environments where you control the originating server. :::warning Request IP Binding Contact support to whitelist your IP addresses. You'll need to provide exact IPs (e.g., 203.0.113.42) or CIDR ranges (e.g., 203.0.113.0/24). ::: ## Common Setup Questions **Q: Should I use TX, RX, or TRX?** Use TRX unless you have a reason to separate send and receive traffic. If you need high throughput, consider multiple TRX connections instead of separate TX/RX pairs. **Q: How do I handle delivery reports?** Listen on your RX or TRX connection for DELIVER_SM PDUs. Each contains the message_id you sent, the destination number, and a final delivery status. **Q: What's the throughput limit?** Standard accounts allow up to 100 messages per second per connection. If you exceed this, you'll receive error code 88. Contact support for higher limits. ## What's next? - [Inbound SMS](inbound-sms) – Receive SMS on your dedicated number - [Credit Check](credit-check) – Monitor your account balance - [MNP Lookup](mnp) – Query mobile number information --- --- title: Inbound SMS sidebar_label: Inbound SMS sidebar_position: 4 description: Receive SMS on a dedicated number with email or HTTP POST delivery --- Rent a dedicated phone number and receive all incoming SMS in real-time. Agile Telecom hosts the SIM and forwards messages to you via email or webhook—perfect for customer support lines, appointment reminders, or automated responses. ## Delivery Methods Choose how you want to receive messages: ### Email Delivery Messages arrive in your inbox automatically from **smsin@agiletelecom.com** with the subject "Agile Telecom -- SMS received". | Field | Format | Example | |-------|--------|---------| | **From** | Sender's phone number | +393301234567 | | **To** | Your rented SIM number | +393331234333 | | **Date/Time** | YYYYMMDDHHMMSS | 20260408143052 | | **Body** | SMS text | News ON | Email is ideal for low-volume messages or integration with email-based systems. ### HTTP POST Webhook Messages post to your webhook URL in real-time. Choose this for automation, databases, and immediate processing. **POST Request Format** Your webhook receives a POST with these form parameters: | Parameter | Description | Example | |-----------|-------------|---------| | originator | Sender's phone number | +393301234567 | | destination | Your SIM number | +393331234333 | | date_time | Reception timestamp (YYYYMMDDHHMMSS) | 20260408143052 | | text | SMS message body | News ON | **Required Response** Your server MUST respond with exactly: ``` +OK ``` If you don't respond with `+OK` within 30 seconds, Agile Telecom treats the delivery as failed and retries after 15 minutes. After 3 failed attempts, the message is discarded. Keep your webhook fast—offload heavy processing to background jobs. ## Webhook Implementation Examples ```python from flask import Flask, request app = Flask(__name__) @app.route('/webhook/sms', methods=['POST']) def receive_sms(): # Extract parameters originator = request.form.get('originator') destination = request.form.get('destination') date_time = request.form.get('date_time') text = request.form.get('text') # Process the message (async in background) print(f"SMS from {originator}: {text}") # Save to database save_to_database(originator, destination, date_time, text) # CRITICAL: respond with +OK immediately return '+OK' def save_to_database(originator, destination, date_time, text): # Your database logic here pass if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` **Deploy with Gunicorn (production):** ```bash pip install flask gunicorn gunicorn -w 4 -b 0.0.0.0:5000 app:app ``` ```javascript const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.urlencoded({ extended: false })); app.post('/webhook/sms', (req, res) => { const { originator, destination, date_time, text } = req.body; // Respond immediately with +OK (CRITICAL) res.send('+OK'); // Process message async processMessage(originator, destination, date_time, text); }); async function processMessage(originator, destination, date_time, text) { console.log(`SMS from ${originator}: ${text}`); // Save to database await saveToDatabase(originator, destination, date_time, text); } async function saveToDatabase(originator, destination, date_time, text) { // Your database logic here } app.listen(5000, () => console.log('Listening on port 5000')); ``` **Run with PM2 (production):** ```bash npm install express body-parser pm2 pm2 start app.js -i max ``` ```php ``` **Run with PHP (production):** ```bash php -S 0.0.0.0:5000 # Or use: supervisor with php-fpm ``` ```java package com.example.sms; @SpringBootApplication public class SmsWebhookApp { public static void main(String[] args) { SpringApplication.run(SmsWebhookApp.class, args); } } @RestController @RequestMapping("/webhook") class SmsController { private static final Logger logger = LoggerFactory.getLogger(SmsController.class); @PostMapping("/sms") public String receiveSms( @RequestParam String originator, @RequestParam String destination, @RequestParam String date_time, @RequestParam String text) { // Respond immediately with +OK (CRITICAL) processMessageAsync(originator, destination, date_time, text); return "+OK"; } @Async public void processMessageAsync(String originator, String destination, String date_time, String text) { logger.info("SMS from {}: {}", originator, text); // Save to database // saveToDatabase(originator, destination, date_time, text); } } ``` **application.properties:** ```properties server.port=5000 spring.task.execution.pool.core-size=10 ``` ```csharp using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; [ApiController] [Route("webhook")] public class SmsController : ControllerBase { private readonly ILogger _logger; private readonly IBackgroundTaskQueue _taskQueue; public SmsController(ILogger logger, IBackgroundTaskQueue taskQueue) { _logger = logger; _taskQueue = taskQueue; } [HttpPost("sms")] public IActionResult ReceiveSms( [FromForm] string originator, [FromForm] string destination, [FromForm] string date_time, [FromForm] string text) { _logger.LogInformation($"SMS from {originator}: {text}"); // Queue async processing _taskQueue.QueueBackgroundWorkItem(async token => { await ProcessMessageAsync(originator, destination, date_time, text); }); // CRITICAL: respond with +OK immediately return Ok("+OK"); } private async Task ProcessMessageAsync(string originator, string destination, string date_time, string text) { // Save to database // await _dbContext.SaveSmsAsync(originator, destination, date_time, text); } } ``` **Startup.cs:** ```csharp public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSingleton(); } ``` ```go package main "fmt" "log" "net/http" ) func receiveSMS(w http.ResponseWriter, r *http.Request) { // Parse POST parameters if err := r.ParseForm(); err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } originator := r.FormValue("originator") destination := r.FormValue("destination") dateTime := r.FormValue("date_time") text := r.FormValue("text") log.Printf("SMS from %s: %s\n", originator, text) // Respond with +OK immediately (CRITICAL) w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) fmt.Fprint(w, "+OK") // Process message asynchronously go processMessage(originator, destination, dateTime, text) } func processMessage(originator, destination, dateTime, text string) { // Save to database or log log.Printf("Processing: %s from %s at %s\n", text, originator, dateTime) } func main() { http.HandleFunc("/webhook/sms", receiveSMS) log.Println("Listening on :5000") log.Fatal(http.ListenAndServe(":5000", nil)) } ``` **Run:** ```bash go run main.go ``` ```ruby require 'sinatra' require 'json' set :port, 5000 set :bind, '0.0.0.0' post '/webhook/sms' do # Extract POST parameters originator = params['originator'] destination = params['destination'] date_time = params['date_time'] text = params['text'] puts "SMS from #{originator}: #{text}" # CRITICAL: respond with +OK immediately # (this happens before the block ends) status 200 headers['Content-Type'] = 'text/plain' body '+OK' # Process message asynchronously Thread.new do save_to_database(originator, destination, date_time, text) end end def save_to_database(originator, destination, date_time, text) # Your database logic here puts "Saved: #{originator} -> #{text}" end __END__ ``` **Run with Puma (production):** ```bash gem install sinatra puma puma -t 5:10 -p 5000 ``` ``` ## Best Practices **Respond Fast** Return `+OK` within 1 second. Process the message in a background queue, not in the webhook handler. **Handle Duplicates** Agile Telecom may retry failed deliveries. Store a hash of the originator + timestamp to detect duplicates and avoid processing the same message twice. **Log Everything** Keep a log of all received messages for debugging and compliance. Include the timestamp, originator, and response time. **Monitor Failures** If delivery fails 3 times, the message is discarded. Monitor your webhook uptime and alert on consecutive failures. **Example: Duplicate Detection (Python)** ```python from datetime import datetime, timedelta processed_messages = {} # In production, use Redis or a database @app.route('/webhook/sms', methods=['POST']) def receive_sms(): originator = request.form.get('originator') date_time = request.form.get('date_time') text = request.form.get('text') # Create unique key message_hash = hashlib.md5(f"{originator}{date_time}".encode()).hexdigest() # Check for duplicate (same sender in last 5 minutes) if message_hash in processed_messages: return '+OK' # Already processed # Mark as processed processed_messages[message_hash] = datetime.now() # Cleanup old entries cutoff = datetime.now() - timedelta(minutes=5) processed_messages = {k: v for k, v in processed_messages.items() if v > cutoff} # Process new message save_to_database(originator, request.form.get('destination'), date_time, text) return '+OK' ``` ## What's next? - [SMPP Protocol](smpp) – Persistent connection protocol for high-volume messaging - [Credit Check](credit-check) – Monitor your account balance - [MNP Lookup](mnp) – Query mobile network information --- --- title: Credit Check sidebar_label: Credit Check sidebar_position: 5 description: Check your SMS account balance via REST API --- ``` ## Overview Monitor your SMS account balance in real-time. Query the Credit Check API to retrieve your remaining account credit in euros before sending messages or bulk campaigns. This is essential for preventing failed shipments due to insufficient funds. **Use cases:** - Check balance before initiating SMS campaigns - Implement automatic notifications when credit falls below a threshold - Validate account status in application dashboards - Prevent failed batch submissions due to low credit ## Making a Request **Endpoint** ``` GET https://secure.agiletelecom.com/services/sms/credit ``` **Authentication** Choose one of three supported methods: - **Basic Auth:** Provide your username and password encoded in the `Authorization` header - **API Key:** Include your API key in the `X-Api-Key` header - **OAuth 1.1:** Use OAuth 1.1 authentication in the `Authorization` header ### Code Examples ``` ```bash # Using API Key authentication curl -X GET "https://secure.agiletelecom.com/services/sms/credit" \ -H "X-Api-Key: YOUR_API_KEY" # Using Basic Auth authentication curl -X GET "https://secure.agiletelecom.com/services/sms/credit" \ -H "Authorization: Basic $(echo -n 'username:password' | base64)" ``` ``` ```python # Using API Key headers = { "X-Api-Key": "YOUR_API_KEY" } response = requests.get( "https://secure.agiletelecom.com/services/sms/credit", headers=headers ) data = response.json() print(f"Your current credit: €{data['credit']}") ``` ``` ```javascript const axios = require('axios'); // Using API Key const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; axios.get('https://secure.agiletelecom.com/services/sms/credit', { headers }) .then(response => { console.log(`Your current credit: €${response.data.credit}`); }) .catch(error => { console.error('Error:', error.response?.data || error.message); }); ``` ``` ```php ``` ``` ```java public class CreditCheck { public static void main(String[] args) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://secure.agiletelecom.com/services/sms/credit")) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); JSONObject json = new JSONObject(response.body()); System.out.println("Your current credit: €" + json.getDouble("credit")); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; class CreditCheck { static async Task Main() { using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); try { HttpResponseMessage response = await client.GetAsync( "https://secure.agiletelecom.com/services/sms/credit"); string content = await response.Content.ReadAsStringAsync(); dynamic data = JsonConvert.DeserializeObject(content); Console.WriteLine($"Your current credit: €{data.credit}"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } } } ``` ``` ```go package main "fmt" "io" "net/http" "encoding/json" ) type CreditResponse struct { Credit float64 `json:"credit"` } func main() { client := &http.Client{} req, _ := http.NewRequest("GET", "https://secure.agiletelecom.com/services/sms/credit", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, err := client.Do(req) if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data CreditResponse json.Unmarshal(body, &data) fmt.Printf("Your current credit: €%.3f\n", data.Credit) } ``` ``` ```ruby require 'net/http' require 'json' uri = URI('https://secure.agiletelecom.com/services/sms/credit') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri.path) request['X-Api-Key'] = 'YOUR_API_KEY' response = http.request(request) data = JSON.parse(response.body) puts "Your current credit: €#{data['credit']}" ``` ``` ## Success Response (2XX) When the request succeeds, the API returns your account balance in euros: ```json { "credit": 10.324 } ``` | Field | Type | Description | |-------|------|-------------| | credit | double | Account credit remaining (in euros) | ## Error Response (5XX) When a server-side error occurs: ```json { "status": "error", "message": "Number check service unavailable", "code": 9 } ``` | Field | Type | Description | |-------|------|-------------| | status | string | Always "error" for 5XX responses | | message | string | Human-readable description of the server-side error | | code | integer | Custom error code indicating the type of problem | ## Error Codes | Code | Description | |------|-------------| | 1 | Wrong credentials | | 2 | Insufficient credit | | 8 | Server error | | 9 | Timeout | | 100 | Source IP is not allowed | :::tip Pro Tip Implement credit monitoring in your application to automatically alert users when balance drops below a configurable threshold. This prevents unexpected service interruptions for high-volume SMS sending. ::: ## What's next? - [SMPP Protocol](smpp) – Persistent connection protocol for high-volume messaging - [Inbound SMS](inbound-sms) – Receive SMS on your dedicated number - [MNP Lookup](mnp) – Query mobile network information --- --- title: MNP Lookup sidebar_label: MNP Lookup sidebar_position: 6 description: Query mobile number portability information via REST API --- ``` ## Overview Query mobile number portability (MNP) information to identify the current operator and network details for any phone number. The MNP Lookup API returns real-time network information including Mobile Country Codes (MCC), Mobile Network Codes (MNC), current operator, and porting status. **Use cases:** - Verify routing before sending SMS to ensure delivery to the correct operator - Detect number porting to optimize routing and prevent delivery delays - Confirm network information for regulatory compliance - Identify operators for intelligent rate optimization - Validate numbers before bulk SMS campaigns ## Making a Request **Endpoint** ``` GET https://mnp.agiletelecom.com/services/mnp/number-lookup?msisdn=+39333123123 ``` **Authentication** Choose one of three supported methods: - **Basic Auth:** Provide your username and password encoded in the `Authorization` header - **API Key:** Include your API key in the `X-Api-Key` header - **OAuth 1.1:** Use OAuth 1.1 authentication in the `Authorization` header **Query Parameters** | Parameter | Type | Description | |-----------|------|-------------| | msisdn | string | The telephone number to query. The leading "+" may be omitted, but you must include the country code. Examples: `+39333123123` or `39333123123` | ### Code Examples ``` ```bash # Using API Key authentication curl -X GET "https://mnp.agiletelecom.com/services/mnp/number-lookup?msisdn=%2B39333123123" \ -H "X-Api-Key: YOUR_API_KEY" # Using Basic Auth authentication curl -X GET "https://mnp.agiletelecom.com/services/mnp/number-lookup?msisdn=%2B39333123123" \ -H "Authorization: Basic $(echo -n 'username:password' | base64)" ``` ``` ```python # Using API Key headers = { "X-Api-Key": "YOUR_API_KEY" } params = { "msisdn": "+39333123123" } response = requests.get( "https://mnp.agiletelecom.com/services/mnp/number-lookup", headers=headers, params=params ) data = response.json() print(f"Operator: {data['operator']}") print(f"Country: {data['country']}") print(f"Ported: {data['ported']}") ``` ``` ```javascript const axios = require('axios'); // Using API Key const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; const params = { msisdn: '+39333123123' }; axios.get('https://mnp.agiletelecom.com/services/mnp/number-lookup', { headers, params }) .then(response => { console.log(`Operator: ${response.data.operator}`); console.log(`Country: ${response.data.country}`); console.log(`Ported: ${response.data.ported}`); }) .catch(error => { console.error('Error:', error.response?.data || error.message); }); ``` ``` ```php ``` ``` ```java public class MNPLookup { public static void main(String[] args) throws Exception { String msisdn = URLEncoder.encode("+39333123123", StandardCharsets.UTF_8); HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://mnp.agiletelecom.com/services/mnp/number-lookup?msisdn=" + msisdn)) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); JSONObject json = new JSONObject(response.body()); System.out.println("Operator: " + json.getString("operator")); System.out.println("Country: " + json.getString("country")); System.out.println("Ported: " + json.getBoolean("ported")); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Web; using System.Threading.Tasks; using Newtonsoft.Json; class MNPLookup { static async Task Main() { using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); try { string msisdn = HttpUtility.UrlEncode("+39333123123"); string url = $"https://mnp.agiletelecom.com/services/mnp/number-lookup?msisdn={msisdn}"; HttpResponseMessage response = await client.GetAsync(url); string content = await response.Content.ReadAsStringAsync(); dynamic data = JsonConvert.DeserializeObject(content); Console.WriteLine($"Operator: {data.@operator}"); Console.WriteLine($"Country: {data.country}"); Console.WriteLine($"Ported: {data.ported}"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } } } ``` ``` ```go package main "fmt" "io" "net/http" "net/url" "encoding/json" ) type MNPResponse struct { Number string `json:"number"` MCC string `json:"mcc"` MNC string `json:"mnc"` NumberType string `json:"numbertype"` Ported bool `json:"ported"` Operator string `json:"operator"` Country string `json:"country"` } func main() { params := url.Values{} params.Add("msisdn", "+39333123123") client := &http.Client{} req, _ := http.NewRequest("GET", "https://mnp.agiletelecom.com/services/mnp/number-lookup?"+params.Encode(), nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, err := client.Do(req) if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data MNPResponse json.Unmarshal(body, &data) fmt.Printf("Operator: %s\n", data.Operator) fmt.Printf("Country: %s\n", data.Country) fmt.Printf("Ported: %v\n", data.Ported) } ``` ``` ```ruby require 'net/http' require 'uri' require 'json' uri = URI('https://mnp.agiletelecom.com/services/mnp/number-lookup') uri.query = URI.encode_www_form('msisdn' => '+39333123123') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri.request_uri) request['X-Api-Key'] = 'YOUR_API_KEY' response = http.request(request) data = JSON.parse(response.body) puts "Operator: #{data['operator']}" puts "Country: #{data['country']}" puts "Ported: #{data['ported']}" ``` ``` ## Success Response (2XX) When the lookup succeeds, the API returns network and operator information: ```json { "number": "+39333123123", "mcc": "222", "mnc": "10", "numbertype": "mobile", "ported": false, "operator": "Vodafone", "country": "Italia" } ``` | Field | Type | Description | |-------|------|-------------| | number | string | The queried telephone number in international format | | mcc | string | Mobile Country Code currently associated with the number | | mnc | string | Mobile Network Code currently associated with the number | | numbertype | string | Fixed value "mobile". Indicates the type of number | | ported | boolean | `true` if the subscriber has transferred their number to a different operator; `false` otherwise | | operator | string | Current operator name of the queried number | | country | string | Country name currently associated with the number | ## Client Error Response (4XX) When the request is invalid or missing required parameters: ```json { "error_code": 400, "error_description": "Specify a msisdn number" } ``` | Field | Type | Description | |-------|------|-------------| | error_code | integer | HTTP error code returned | | error_description | string | Description related to the error code | **Common 4XX Errors** - `400`: Missing or invalid msisdn parameter - `400`: Invalid number format ## Server Error Response (5XX) When a server-side error occurs: ```json { "error_code": 500, "error_description": "Server error" } ``` | Field | Type | Description | |-------|------|-------------| | error_code | integer | HTTP error code returned | | error_description | string | Description related to the error code | :::tip Ported Numbers & Routing The `ported` field indicates whether the subscriber has transferred their number to a different operator. Use this information for intelligent routing decisions, load balancing across operators, and delivery optimization. Ported numbers may route through different gateways depending on the current operator. ::: ## What's next? - [SMPP Protocol](smpp) – Persistent connection protocol for high-volume messaging - [Inbound SMS](inbound-sms) – Receive SMS on your dedicated number - [Credit Check](credit-check) – Monitor your account balance --- --- title: RCS API sidebar_position: 7 description: Send rich RCS messages with images, buttons, and multimedia content via REST API --- # RCS API RCS (Rich Communication Services) is the evolution of smartphone messaging. Unlike SMS, it enables multimedia content, Call to Action buttons (CTA), and bidirectional conversations with a verified sender called an RCS Agent. ## Why use RCS - **Visible brand**: RCS Agent name and icons - **Rich content**: text, images, cards, and carousels - **Interactive buttons**: links, calls, guided replies - **Two-way conversation**: manage and respond to user interactions ## Use cases - Rich notifications with images and buttons - Promotions, offers, and shopping carts (cards/carousels) - Conversational customer support - Campaigns with high engagement :::info User requirements Compatible smartphone and messaging app with RCS support; active internet connection. ::: ## Core concepts ### Message types - **TEXT**: text message with optional buttons - **CARD**: single rich card with image, title, description, and actions - **CAROUSEL**: scrollable list of 2-10 cards for catalogs or menus ### Suggestions (CTA) Action buttons users can select: - **reply**: guided text response - **url**: link opening - **dial**: phone call initiation - **coordinates**: coordinate sharing - **location**: location search - **calendar**: calendar event creation ### Media Cards support images. Provide public, valid URLs for the entire send duration. Always set an appropriate height value. ### Placeholders Use variables in text (e.g., `{name}`). If you don't provide values during send, the variable appears unsubstituted. ### SMS Fallback Optional. If a contact isn't reachable via RCS, send a fallback SMS instead (sender must be authorized). ## Authentication and security - **Protocol**: HTTPS required - **Authentication**: API Key in header (e.g., `X-Api-Key`) ## Quickstart Get up and running in three steps: 1. **List RCS agents** to find your agent ID 2. **Send a message** using a template or free-form content 3. **Receive outcomes** via webhook for Delivery/Read events and handle Inbound responses :::info Template guide For the complete JSON body structure of templates (TEXT, CARD, CAROUSEL), Suggestions, Media, Placeholders, and SMS Fallback, see [RCS Templates](./rcs-templates.md). ::: ## RCS Agents ### List agents Retrieve all RCS agents configured for your account. You'll need an `agentId` to send any message. ``` ```bash curl -X GET https://lora-api.agiletelecom.com/api/message-server/rcs/agents \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python headers = { "X-Api-Key": "YOUR_API_KEY" } response = requests.get( "https://lora-api.agiletelecom.com/api/message-server/rcs/agents", headers=headers ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/agents', { method: 'GET', headers: headers }) .then(response => response.json()) .then(data => console.log(data)); ``` ``` ```php ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/agents")) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using System; using System.Net.Http; using System.Threading.Tasks; var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); try { var response = await client.GetAsync("https://lora-api.agiletelecom.com/api/message-server/rcs/agents"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } ``` ``` ```go package main "fmt" "io/ioutil" "net/http" ) func main() { client := &http.Client{} req, _ := http.NewRequest("GET", "https://lora-api.agiletelecom.com/api/message-server/rcs/agents", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/agents") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts JSON.parse(response.body) ``` ``` **Response**: An array of agent objects with the `agentId` field. Copy the `agentId` you want to use for sending messages. ## RCS Templates (CRUD) Templates let you reuse message designs across campaigns. Store them once, send thousands of times. See [RCS Templates](./rcs-templates.md) for the complete JSON structure. ### List templates Retrieve all templates for your account in paginated format. Useful for building a template picker in your UI. ``` ```bash curl -X GET "https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1" \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python headers = {"X-Api-Key": "YOUR_API_KEY"} params = {"limit": 10, "page": 1} response = requests.get( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates", headers=headers, params=params ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; const url = 'https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1'; fetch(url, { method: 'GET', headers }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true ]); $response = curl_exec($ch); curl_close($ch); echo json_decode($response, true); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1")) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var response = await client.GetAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1" ); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); ``` ``` ```go client := &http.Client{} req, _ := http.NewRequest("GET", "https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/templates?limit=10&page=1") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts JSON.parse(response.body) ``` ``` **Parameters**: - `limit`: number of records per page (e.g., 10) - `page`: page number (starts at 1) ### Get template details Fetch a single template by ID to see its complete structure before sending. Helpful for previewing template content or validating placeholders. ``` ```bash curl -X GET https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123 \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python headers = {"X-Api-Key": "YOUR_API_KEY"} template_id = "tpl_123" response = requests.get( f"https://lora-api.agiletelecom.com/api/message-server/rcs/templates/{template_id}", headers=headers ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; const templateId = 'tpl_123'; fetch(`https://lora-api.agiletelecom.com/api/message-server/rcs/templates/${templateId}`, { method: 'GET', headers }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java String templateId = "tpl_123"; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/" + templateId)) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var response = await client.GetAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123" ); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); ``` ``` ```go client := &http.Client{} req, _ := http.NewRequest("GET", "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Create template Store a reusable template for sending the same message to many recipients. See [RCS Templates](./rcs-templates.md) for body structure details. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/templates \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Welcome Template", "type": "TEXT", "body": { "text": "Welcome to our service!" } }' ``` ``` ```python headers = { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } body = { "name": "Welcome Template", "type": "TEXT", "body": {"text": "Welcome to our service!"} } response = requests.post( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates", headers=headers, json=body ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const body = { name: 'Welcome Template', type: 'TEXT', body: { text: 'Welcome to our service!' } }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/templates', { method: 'POST', headers, body: JSON.stringify(body) }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php "Welcome Template", "type" => "TEXT", "body" => ["text" => "Welcome to our service!"] ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java String jsonPayload = """ { "name": "Welcome Template", "type": "TEXT", "body": { "text": "Welcome to our service!" } } """; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/templates")) .header("X-Api-Key", "YOUR_API_KEY") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp using System.Text.Json; var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var payload = new { name = "Welcome Template", type = "TEXT", body = new { text = "Welcome to our service!" } }; var content = new StringContent( JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json" ); var response = await client.PostAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ``` ```go payload := `{ "name": "Welcome Template", "type": "TEXT", "body": { "text": "Welcome to our service!" } }` client := &http.Client{} req, _ := http.NewRequest("POST", "https://lora-api.agiletelecom.com/api/message-server/rcs/templates", strings.NewReader(payload)) req.Header.Add("X-Api-Key", "YOUR_API_KEY") req.Header.Add("Content-Type", "application/json") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/templates") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true payload = { name: 'Welcome Template', type: 'TEXT', body: { text: 'Welcome to our service!' } } request = Net::HTTP::Post.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" request["Content-Type"] = "application/json" request.body = JSON.generate(payload) response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Update template Modify an existing template. All subsequent sends using this template will use the updated content. ``` ```bash curl -X PUT https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123 \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Welcome Template", "type": "TEXT", "body": { "text": "Welcome back!" } }' ``` ``` ```python headers = { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } payload = { "name": "Updated Welcome Template", "type": "TEXT", "body": {"text": "Welcome back!"} } response = requests.put( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", headers=headers, json=payload ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const payload = { name: 'Updated Welcome Template', type: 'TEXT', body: { text: 'Welcome back!' } }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123', { method: 'PUT', headers, body: JSON.stringify(payload) }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php "Updated Welcome Template", "type" => "TEXT", "body" => ["text" => "Welcome back!"] ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java String jsonPayload = """ { "name": "Updated Welcome Template", "type": "TEXT", "body": { "text": "Welcome back!" } } """; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123")) .header("X-Api-Key", "YOUR_API_KEY") .header("Content-Type", "application/json") .method("PUT", HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var payload = new { name = "Updated Welcome Template", type = "TEXT", body = new { text = "Welcome back!" } }; var content = new StringContent( JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json" ); var response = await client.PutAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ``` ```go payload := `{ "name": "Updated Welcome Template", "type": "TEXT", "body": { "text": "Welcome back!" } }` client := &http.Client{} req, _ := http.NewRequest("PUT", "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", strings.NewReader(payload)) req.Header.Add("X-Api-Key", "YOUR_API_KEY") req.Header.Add("Content-Type", "application/json") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true payload = { name: 'Updated Welcome Template', type: 'TEXT', body: { text: 'Welcome back!' } } request = Net::HTTP::Put.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" request["Content-Type"] = "application/json" request.body = JSON.generate(payload) response = http.request(request) puts JSON.parse(response.body) ``` ``` ### Delete template Remove a template from your account. Messages already sent won't be affected, but you won't be able to send new messages using this template. ``` ```bash curl -X DELETE https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123 \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python headers = {"X-Api-Key": "YOUR_API_KEY"} response = requests.delete( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", headers=headers ) print(response.status_code) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123', { method: 'DELETE', headers }) .then(r => console.log(r.status)); ``` ``` ```php $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => "DELETE", CURLOPT_RETURNTRANSFER => true ]); echo curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123")) .header("X-Api-Key", "YOUR_API_KEY") .DELETE() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var response = await client.DeleteAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123" ); Console.WriteLine((int)response.StatusCode); ``` ``` ```go client := &http.Client{} req, _ := http.NewRequest("DELETE", "https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, _ := client.Do(req) fmt.Println(resp.StatusCode) ``` ``` ```ruby require 'net/http' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/templates/tpl_123") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Delete.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts response.code ``` ``` ## Sending messages ### Send from template Send a message using a previously saved template. Use `messageId` to track delivery reports and read events. Optionally schedule the send for a future date. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/send \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "templateId": "tpl_123", "destination": "+391234567890", "agentId": "agent_abc", "messageId": "ext-unique-id-001", "placeholders": {"name": "Anna"}, "simulation": false, "scheduledDate": "2026-04-15T10:30:00Z" }' ``` ``` ```python from datetime import datetime headers = { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } payload = { "templateId": "tpl_123", "destination": "+391234567890", "agentId": "agent_abc", "messageId": "ext-unique-id-001", "placeholders": {"name": "Anna"}, "simulation": False, "scheduledDate": "2026-04-15T10:30:00Z" } response = requests.post( "https://lora-api.agiletelecom.com/api/message-server/rcs/send", headers=headers, json=payload ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const payload = { templateId: 'tpl_123', destination: '+391234567890', agentId: 'agent_abc', messageId: 'ext-unique-id-001', placeholders: { name: 'Anna' }, simulation: false, scheduledDate: '2026-04-15T10:30:00Z' }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/send', { method: 'POST', headers, body: JSON.stringify(payload) }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php "tpl_123", "destination" => "+391234567890", "agentId" => "agent_abc", "messageId" => "ext-unique-id-001", "placeholders" => ["name" => "Anna"], "simulation" => false, "scheduledDate" => "2026-04-15T10:30:00Z" ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java String jsonPayload = """ { "templateId": "tpl_123", "destination": "+391234567890", "agentId": "agent_abc", "messageId": "ext-unique-id-001", "placeholders": { "name": "Anna" }, "simulation": false, "scheduledDate": "2026-04-15T10:30:00Z" } """; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/send")) .header("X-Api-Key", "YOUR_API_KEY") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var payload = new { templateId = "tpl_123", destination = "+391234567890", agentId = "agent_abc", messageId = "ext-unique-id-001", placeholders = new { name = "Anna" }, simulation = false, scheduledDate = "2026-04-15T10:30:00Z" }; var content = new StringContent( JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json" ); var response = await client.PostAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ``` ```go payload := `{ "templateId": "tpl_123", "destination": "+391234567890", "agentId": "agent_abc", "messageId": "ext-unique-id-001", "placeholders": { "name": "Anna" }, "simulation": false, "scheduledDate": "2026-04-15T10:30:00Z" }` client := &http.Client{} req, _ := http.NewRequest("POST", "https://lora-api.agiletelecom.com/api/message-server/rcs/send", strings.NewReader(payload)) req.Header.Add("X-Api-Key", "YOUR_API_KEY") req.Header.Add("Content-Type", "application/json") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true payload = { templateId: 'tpl_123', destination: '+391234567890', agentId: 'agent_abc', messageId: 'ext-unique-id-001', placeholders: { name: 'Anna' }, simulation: false, scheduledDate: '2026-04-15T10:30:00Z' } request = Net::HTTP::Post.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" request["Content-Type"] = "application/json" request.body = JSON.generate(payload) response = http.request(request) puts JSON.parse(response.body) ``` ``` | Field | Type | Required | Description | |-------|------|----------|------------| | templateId | string | Yes | Template ID to use | | destination | string | Yes | Recipient phone number | | agentId | string | Yes | RCS Agent ID | | messageId | string | Yes | Unique identifier for idempotency | | placeholders | object | No | Variables to replace in template | | simulation | boolean | No | Test mode (default: false) | | scheduledDate | string (ISO-8601) | No | Scheduled send date/time | ### Send without template (free-form) Send a message on-the-fly without storing it as a template. Useful for dynamic or one-off messages. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/send \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_abc", "destination": "+391234567890", "messageId": "ext-unique-id-002", "body": { "type": "TEXT", "text": "Your order #12345 is ready for pickup!" } }' ``` ``` ```python headers = { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" } payload = { "agentId": "agent_abc", "destination": "+391234567890", "messageId": "ext-unique-id-002", "body": { "type": "TEXT", "text": "Your order #12345 is ready for pickup!" } } response = requests.post( "https://lora-api.agiletelecom.com/api/message-server/rcs/send", headers=headers, json=payload ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const payload = { agentId: 'agent_abc', destination: '+391234567890', messageId: 'ext-unique-id-002', body: { type: 'TEXT', text: 'Your order #12345 is ready for pickup!' } }; fetch('https://lora-api.agiletelecom.com/api/message-server/rcs/send', { method: 'POST', headers, body: JSON.stringify(payload) }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php "agent_abc", "destination" => "+391234567890", "messageId" => "ext-unique-id-002", "body" => [ "type" => "TEXT", "text" => "Your order #12345 is ready for pickup!" ] ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java String jsonPayload = """ { "agentId": "agent_abc", "destination": "+391234567890", "messageId": "ext-unique-id-002", "body": { "type": "TEXT", "text": "Your order #12345 is ready for pickup!" } } """; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/message-server/rcs/send")) .header("X-Api-Key", "YOUR_API_KEY") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var payload = new { agentId = "agent_abc", destination = "+391234567890", messageId = "ext-unique-id-002", body = new { type = "TEXT", text = "Your order #12345 is ready for pickup!" } }; var content = new StringContent( JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json" ); var response = await client.PostAsync( "https://lora-api.agiletelecom.com/api/message-server/rcs/send", content ); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` ``` ```go payload := `{ "agentId": "agent_abc", "destination": "+391234567890", "messageId": "ext-unique-id-002", "body": { "type": "TEXT", "text": "Your order #12345 is ready for pickup!" } }` client := &http.Client{} req, _ := http.NewRequest("POST", "https://lora-api.agiletelecom.com/api/message-server/rcs/send", strings.NewReader(payload)) req.Header.Add("X-Api-Key", "YOUR_API_KEY") req.Header.Add("Content-Type", "application/json") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/message-server/rcs/send") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true payload = { agentId: 'agent_abc', destination: '+391234567890', messageId: 'ext-unique-id-002', body: { type: 'TEXT', text: 'Your order #12345 is ready for pickup!' } } request = Net::HTTP::Post.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" request["Content-Type"] = "application/json" request.body = JSON.generate(payload) response = http.request(request) puts JSON.parse(response.body) ``` ``` For the full body structure including CARD, CAROUSEL, Suggestions, Media, Placeholders, and SMS Fallback, see [RCS Templates](./rcs-templates.md). ## Scheduling and simulation - **scheduledDate**: ISO-8601 format; timezone handling is the client's responsibility - **simulation**: Test mode; responses indicate platform acceptance but no actual send occurs :::warning SMS Fallback Requires an authorized SMS sender. Authorization can be requested free of charge in the platform. ::: ## Asynchronous events: Delivery and Read Final outcomes don't arrive in the immediate response but via webhook to your configured endpoint. The send response only indicates acceptance. ### Delivery Message delivery notification. ```json { "channel": "RCS", "eventType": "DELIVERY", "messageId": "ext-unique-id-001", "destination": "+391234567890", "statusCode": "DELIVERED", "description": "Message delivered", "timestamp": "2025-11-12T10:30:10Z" } ``` ### Read Message read notification. ```json { "channel": "RCS", "eventType": "READ", "messageId": "ext-unique-id-001", "destination": "+391234567890", "timestamp": "2025-11-12T10:32:45Z" } ``` :::info Webhook retries The platform performs up to five retries spaced several minutes apart. If you don't respond correctly, retries stop after the fifth attempt. ::: ## Inbound messages and Media User responses and multimedia content arrive at your configured Inbound endpoint. ### Inbound text Contains text and any emojis inserted by the user. ```json { "channel": "RCS", "messageType": "TEXT", "source": "+391234567890", "destination": "agent_abc", "receivedDate": "2025-11-12T10:35:20Z", "text": "Yes, I'm interested" } ``` ### Inbound with media Contains a key (mediaKey) to retrieve an image or file sent by the user. ```json { "channel": "RCS", "messageType": "IMAGE", "source": "+391234567890", "destination": "agent_abc", "receivedDate": "2025-11-12T10:36:05Z", "mediaKey": "rcs-media-001" } ``` ### Download media Fetch the media file using the key from an inbound message. The temporary download URL expires after the specified minutes. ``` ```bash curl -X GET "https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15" \ -H "X-Api-Key: YOUR_API_KEY" ``` ``` ```python headers = {"X-Api-Key": "YOUR_API_KEY"} params = {"expireMinutes": 15} response = requests.get( "https://lora-api.agiletelecom.com/api/files/rcs-media-001", headers=headers, params=params ) print(response.json()) ``` ``` ```javascript const fetch = require('node-fetch'); const headers = { 'X-Api-Key': 'YOUR_API_KEY' }; fetch('https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15', { method: 'GET', headers }) .then(r => r.json()) .then(data => console.log(data)); ``` ``` ```php $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true ]); echo json_decode(curl_exec($ch), true); curl_close($ch); ?> ``` ``` ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15")) .header("X-Api-Key", "YOUR_API_KEY") .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); ``` ``` ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY"); var response = await client.GetAsync( "https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15" ); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); ``` ``` ```go client := &http.Client{} req, _ := http.NewRequest("GET", "https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15", nil) req.Header.Add("X-Api-Key", "YOUR_API_KEY") resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) ``` ``` ```ruby require 'net/http' require 'json' uri = URI("https://lora-api.agiletelecom.com/api/files/rcs-media-001?expireMinutes=15") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) request["X-Api-Key"] = "YOUR_API_KEY" response = http.request(request) puts JSON.parse(response.body) ``` ``` **Response**: A public and temporary URL for downloading the media file. ## Best practices - **Placeholders**: Always provide required values; otherwise substitution won't occur - **Media**: Use public and stable URLs for the entire campaign duration; always set an appropriate height value - **Suggestions**: Respect the required fields for each action type - **Idempotency**: Always use unique messageIds - **Error handling**: Handle 4xx (input errors) and 5xx (temporary errors) with exponential backoff ## Postman Collection Import the official collection to quickly test all endpoints in an interactive environment. ### Step 1: Import the collection 1. Open Postman → Import 2. Select the file: [AgileTelecom RCS API.postman_collection.json](https://drive.google.com/drive/folders/1kb4fwyL8q9gFdqbnvbRU3IDXtlADvGJ_?usp=sharing) 3. Confirm with Import The collection includes requests for Agents, Template CRUD, Sending, example webhooks, and media download. ### Step 2: Configure collection variables 1. Click the **AgileTelecom RCS API** collection 2. Open the **Variables** tab and fill in: | Variable | Value | Use | |----------|-------|-----| | BASE_URL | `https://lora-api.agiletelecom.com` | Base URL for all requests | | API_KEY | Your API key | Authentication | | DESTINATION | `+391234567890` | Recipient number | | AGENT_ID | Agent ID (from GET /rcs/agents) | Message sending | | TEMPLATE_ID_TEXT | TEXT template ID | Test sending | | TEMPLATE_ID_CARD | CARD template ID | Test sending | | SCHEDULED_DATE | `2026-04-30T18:50:00Z` | Scheduled sends | | MESSAGE_ID | `uuid-1` | Unique message ID | | MEDIA_KEY | Media key | Inbound media download | :::warning Important note If after import you find BASE_URL with a different value, replace it with `https://lora-api.agiletelecom.com` in the collection **Variables** tab. Remember the `X-Api-Key: {{API_KEY}}` header in requests that require it. ::: ### Step 3: Execute requests - **Agents**: `GET {{BASE_URL}}/api/message-server/rcs/agents` - **Templates (CRUD)**: - List: `GET {{BASE_URL}}/api/message-server/rcs/templates?limit=10&page=1` - Details: `GET {{BASE_URL}}/api/message-server/rcs/templates/:id` - Create: `POST {{BASE_URL}}/api/message-server/rcs/templates` - Update: `PUT {{BASE_URL}}/api/message-server/rcs/templates/:id` - Delete: `DELETE {{BASE_URL}}/api/message-server/rcs/templates/:id` - **Sending**: - From template: `POST {{BASE_URL}}/api/message-server/rcs/send` - Free-form: `POST {{BASE_URL}}/api/message-server/rcs/send` with complete body - **Media**: `GET {{BASE_URL}}/api/files/:mediaKey?expireMinutes=180` Example template-based send: ```http POST {{BASE_URL}}/api/message-server/rcs/send X-Api-Key: {{API_KEY}} Content-Type: application/json { "templateId": "{{TEMPLATE_ID_TEXT}}", "destination": "{{DESTINATION}}", "agentId": "{{AGENT_ID}}", "messageId": "{{MESSAGE_ID}}", "placeholders": {"name": "Anna"} } ``` ## Troubleshooting **Q: I see {name} in the received message. Why?** A: The placeholder value is missing from the send request. Always provide values for all placeholders used in your template, or remove the variable from the template. **Q: The image won't load in my message.** A: Verify the image URL is publicly accessible and that you've set the height property correctly in your template. **Q: The send response says "accepted". Did the send succeed?** A: "Accepted" means the platform queued your message. Final delivery status arrives later via the DELIVERY webhook event. **Q: SMS fallback isn't sending.** A: Ensure the SMS sender is authorized in the platform. You can request authorization through your admin dashboard at no charge. **Q: How do I check if a recipient supports RCS?** A: Use the SMS fallback feature to handle RCS-unreachable contacts. Observe DELIVERY status codes to identify which contacts received the message via RCS versus SMS. ## What's next? - See [RCS Templates](./rcs-templates.md) for the complete template structure and field reference - Configure webhooks to receive Delivery/Read events and Inbound responses - Test your integration with the Postman Collection --- --- title: RCS Templates sidebar_position: 8 description: Complete JSON structure for RCS templates (TEXT, CARD, CAROUSEL) with multi-language examples and best practices --- # RCS Templates Learn how to create and structure RCS templates (TEXT, CARD, CAROUSEL) using the POST `/api/message-server/rcs/templates` endpoint. This guide covers JSON structure, field requirements, and real-world examples in multiple programming languages. ## Base payload structure Every RCS template includes a name, description, type, and body. The body structure varies by template type. ```json { "name": "Human-readable name", "description": "Brief template description", "type": "TEXT | CARD | CAROUSEL", "body": { /* template content */ } } ``` | Field | Type | Required | Notes | |-------|------|----------|-------| | `name` | string | Yes | Display name for your template | | `description` | string | Yes | Clear description of the template's purpose | | `type` | string | Yes | One of: `TEXT`, `CARD`, `CAROUSEL` | | `body` | object | Yes | Template structure (varies by type) | ## Common fields (in body) ### SMS Fallback (optional) Provide an SMS alternative that sends automatically if RCS isn't available on the recipient's device. Both `sender` and `text` are required if you define `fallbackSms`. ```json "fallbackSms": { "sender": "SMS-SENDER", "text": "SMS message text" } ``` :::info SMS Fallback Requirements - `sender`: Your authorized SMS sender ID (typically 4-11 alphanumeric characters) - `text`: Fallback message content (must match template language and tone) ::: ### Suggestions (optional) Add interactive buttons and actions that recipients can tap. Each suggestion defines the action type and required fields: - **type**: action type (`url`, `dial`, `reply`, `coordinates`, `location`, `calendar`) - **text**: button label (displays to the user) ```json "suggestions": [ { "type": "url", "text": "Visit website", "url": { "url": "https://example.com" } }, { "type": "dial", "text": "Call us", "dial": { "phoneNumber": "+39037224525" } } ] ``` **Best practices for suggestions:** - Keep button text concise (under 25 characters) - Use action-oriented labels ("Call us", "Learn more", "Shop now") - Include 1-4 suggestions per template for optimal UX - Ensure phone numbers include country code ## TEXT Template Send rich text messages with optional interactive suggestions and SMS fallback. Perfect for confirmations, alerts, and personalized messages. ### Structure ```json { "type": "TEXT", "body": { "text": "Message text", "suggestions": [ ... ], "fallbackSms": { ... } } } ``` | Field | Required | Notes | |-------|----------|-------| | `text` | Yes | Non-empty message content (supports placeholders like `{name}`) | | `suggestions` | No | Array of interactive buttons and actions | | `fallbackSms` | No | SMS alternative if RCS unavailable | ### Create a TEXT template This example creates a welcome message with a website link, call button, and SMS fallback. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/templates \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Welcome Message", "description": "Personalized welcome with SMS fallback", "type": "TEXT", "body": { "text": "Hi {name}, welcome to our service!", "suggestions": [ { "type": "url", "text": "Visit our site", "url": { "url": "https://agiletelecom.com" } }, { "type": "dial", "text": "Call us", "dial": { "phoneNumber": "+39037224525" } } ], "fallbackSms": { "sender": "AGILE", "text": "Hi {name}, welcome to our service!" } } }' ``` ``` ```python api_key = "YOUR_API_KEY" base_url = "https://lora-api.agiletelecom.com" payload = { "name": "Welcome Message", "description": "Personalized welcome with SMS fallback", "type": "TEXT", "body": { "text": "Hi {name}, welcome to our service!", "suggestions": [ { "type": "url", "text": "Visit our site", "url": {"url": "https://agiletelecom.com"} }, { "type": "dial", "text": "Call us", "dial": {"phoneNumber": "+39037224525"} } ], "fallbackSms": { "sender": "AGILE", "text": "Hi {name}, welcome to our service!" } } } response = requests.post( f"{base_url}/api/message-server/rcs/templates", headers={"X-Api-Key": api_key}, json=payload ) template = response.json() print(f"Template created: {template['id']}") ``` ``` ```javascript const fetch = require('node-fetch'); const apiKey = 'YOUR_API_KEY'; const baseUrl = 'https://lora-api.agiletelecom.com'; const payload = { name: 'Welcome Message', description: 'Personalized welcome with SMS fallback', type: 'TEXT', body: { text: 'Hi {name}, welcome to our service!', suggestions: [ { type: 'url', text: 'Visit our site', url: { url: 'https://agiletelecom.com' } }, { type: 'dial', text: 'Call us', dial: { phoneNumber: '+39037224525' } } ], fallbackSms: { sender: 'AGILE', text: 'Hi {name}, welcome to our service!' } } }; fetch(`${baseUrl}/api/message-server/rcs/templates`, { method: 'POST', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => console.log(`Template created: ${data.id}`)); ``` ``` ```php 'Welcome Message', 'description' => 'Personalized welcome with SMS fallback', 'type' => 'TEXT', 'body' => [ 'text' => 'Hi {name}, welcome to our service!', 'suggestions' => [ [ 'type' => 'url', 'text' => 'Visit our site', 'url' => ['url' => 'https://agiletelecom.com'] ], [ 'type' => 'dial', 'text' => 'Call us', 'dial' => ['phoneNumber' => '+39037224525'] ] ], 'fallbackSms' => [ 'sender' => 'AGILE', 'text' => 'Hi {name}, welcome to our service!' ] ] ]; $ch = curl_init("$baseUrl/api/message-server/rcs/templates"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $apiKey, 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); $response = curl_exec($ch); $template = json_decode($response, true); echo "Template created: " . $template['id']; ?> ``` ``` ```java public class CreateTextTemplate { public static void main(String[] args) throws Exception { String apiKey = "YOUR_API_KEY"; String baseUrl = "https://lora-api.agiletelecom.com"; JsonObject payload = new JsonObject(); payload.addProperty("name", "Welcome Message"); payload.addProperty("description", "Personalized welcome with SMS fallback"); payload.addProperty("type", "TEXT"); JsonObject body = new JsonObject(); body.addProperty("text", "Hi {name}, welcome to our service!"); JsonArray suggestions = new JsonArray(); JsonObject urlSuggestion = new JsonObject(); urlSuggestion.addProperty("type", "url"); urlSuggestion.addProperty("text", "Visit our site"); JsonObject urlObj = new JsonObject(); urlObj.addProperty("url", "https://agiletelecom.com"); urlSuggestion.add("url", urlObj); suggestions.add(urlSuggestion); JsonObject dialSuggestion = new JsonObject(); dialSuggestion.addProperty("type", "dial"); dialSuggestion.addProperty("text", "Call us"); JsonObject dialObj = new JsonObject(); dialObj.addProperty("phoneNumber", "+39037224525"); dialSuggestion.add("dial", dialObj); suggestions.add(dialSuggestion); body.add("suggestions", suggestions); JsonObject fallbackSms = new JsonObject(); fallbackSms.addProperty("sender", "AGILE"); fallbackSms.addProperty("text", "Hi {name}, welcome to our service!"); body.add("fallbackSms", fallbackSms); payload.add("body", body); HttpRequest request = HttpRequest.newBuilder() .uri(new java.net.URI(baseUrl + "/api/message-server/rcs/templates")) .header("X-Api-Key", apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload.toString())) .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); JsonObject result = JsonParser.parseString(response.body()).getAsJsonObject(); System.out.println("Template created: " + result.get("id")); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Text; using Newtonsoft.Json; public class CreateTextTemplate { public static async Task Main() { string apiKey = "YOUR_API_KEY"; string baseUrl = "https://lora-api.agiletelecom.com"; var payload = new { name = "Welcome Message", description = "Personalized welcome with SMS fallback", type = "TEXT", body = new { text = "Hi {name}, welcome to our service!", suggestions = new object[] { new { type = "url", text = "Visit our site", url = new { url = "https://agiletelecom.com" } }, new { type = "dial", text = "Call us", dial = new { phoneNumber = "+39037224525" } } }, fallbackSms = new { sender = "AGILE", text = "Hi {name}, welcome to our service!" } } }; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); var json = JsonConvert.SerializeObject(payload); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( $"{baseUrl}/api/message-server/rcs/templates", content ); var responseBody = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(responseBody); Console.WriteLine($"Template created: {result.id}"); } } } ``` ``` ```go package main "bytes" "encoding/json" "fmt" "io" "net/http" ) func main() { apiKey := "YOUR_API_KEY" baseUrl := "https://lora-api.agiletelecom.com" payload := map[string]interface{}{ "name": "Welcome Message", "description": "Personalized welcome with SMS fallback", "type": "TEXT", "body": map[string]interface{}{ "text": "Hi {name}, welcome to our service!", "suggestions": []map[string]interface{}{ { "type": "url", "text": "Visit our site", "url": map[string]string{"url": "https://agiletelecom.com"}, }, { "type": "dial", "text": "Call us", "dial": map[string]string{"phoneNumber": "+39037224525"}, }, }, "fallbackSms": map[string]string{ "sender": "AGILE", "text": "Hi {name}, welcome to our service!", }, }, } jsonData, _ := json.Marshal(payload) req, _ := http.NewRequest( "POST", fmt.Sprintf("%s/api/message-server/rcs/templates", baseUrl), bytes.NewBuffer(jsonData), ) req.Header.Set("X-Api-Key", apiKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result map[string]interface{} json.Unmarshal(body, &result) fmt.Printf("Template created: %v\n", result["id"]) } ``` ``` ```ruby require 'net/http' require 'json' api_key = 'YOUR_API_KEY' base_url = 'https://lora-api.agiletelecom.com' payload = { name: 'Welcome Message', description: 'Personalized welcome with SMS fallback', type: 'TEXT', body: { text: 'Hi {name}, welcome to our service!', suggestions: [ { type: 'url', text: 'Visit our site', url: { url: 'https://agiletelecom.com' } }, { type: 'dial', text: 'Call us', dial: { phoneNumber: '+39037224525' } } ], fallbackSms: { sender: 'AGILE', text: 'Hi {name}, welcome to our service!' } } } uri = URI("#{base_url}/api/message-server/rcs/templates") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request['X-Api-Key'] = api_key request['Content-Type'] = 'application/json' request.body = payload.to_json response = http.request(request) result = JSON.parse(response.body) puts "Template created: #{result['id']}" ``` ``` ## CARD Template Display a single rich card with media, title, description, and interactive actions. Perfect for promotional offers, product showcases, and event announcements. ### Structure ```json { "type": "CARD", "body": { "cardOrientation": "VERTICAL | HORIZONTAL | UNSPECIFIED", "thumbnailAlignment": "LEFT | RIGHT | UNSPECIFIED", "card": { "title": "Title", "description": "Description", "media": { "height": "SHORT | MEDIUM | TALL | UNSPECIFIED", "fileUrl": "https://..." }, "suggestions": [ ... ] }, "suggestions": [ ... ], "fallbackSms": { ... } } } ``` | Field | Required | Notes | |-------|----------|-------| | `cardOrientation` | Yes | Layout: `VERTICAL` (recommended) or `HORIZONTAL` | | `card` | Yes | Card container with title, description, media | | `card.media.height` | If media present | Height: `SHORT`, `MEDIUM`, `TALL` | | `card.media.fileUrl` | If media present | Public URL to image or video (kept online during send) | | `suggestions` | No | Card-level and global-level action buttons | ### Create a CARD template This example creates a promotional card with an image, offer details, and action buttons. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/templates \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Fall Promotion Card", "description": "Vertical card with seasonal offer", "type": "CARD", "body": { "cardOrientation": "VERTICAL", "card": { "title": "Fall promotion", "description": "20% off selected products", "media": { "height": "TALL", "fileUrl": "https://cdn.example.com/img/offer.jpg" }, "suggestions": [ { "type": "url", "text": "Discover offer", "url": { "url": "https://shop.example.com/offer" } } ] }, "suggestions": [ { "type": "dial", "text": "Call us", "dial": { "phoneNumber": "+39037224525" } } ], "fallbackSms": { "sender": "AGILE", "text": "Promo -20% on shop.example.com" } } }' ``` ``` ```python api_key = "YOUR_API_KEY" base_url = "https://lora-api.agiletelecom.com" payload = { "name": "Fall Promotion Card", "description": "Vertical card with seasonal offer", "type": "CARD", "body": { "cardOrientation": "VERTICAL", "card": { "title": "Fall promotion", "description": "20% off selected products", "media": { "height": "TALL", "fileUrl": "https://cdn.example.com/img/offer.jpg" }, "suggestions": [ { "type": "url", "text": "Discover offer", "url": {"url": "https://shop.example.com/offer"} } ] }, "suggestions": [ { "type": "dial", "text": "Call us", "dial": {"phoneNumber": "+39037224525"} } ], "fallbackSms": { "sender": "AGILE", "text": "Promo -20% on shop.example.com" } } } response = requests.post( f"{base_url}/api/message-server/rcs/templates", headers={"X-Api-Key": api_key}, json=payload ) template = response.json() print(f"Template created: {template['id']}") ``` ``` ```javascript const fetch = require('node-fetch'); const apiKey = 'YOUR_API_KEY'; const baseUrl = 'https://lora-api.agiletelecom.com'; const payload = { name: 'Fall Promotion Card', description: 'Vertical card with seasonal offer', type: 'CARD', body: { cardOrientation: 'VERTICAL', card: { title: 'Fall promotion', description: '20% off selected products', media: { height: 'TALL', fileUrl: 'https://cdn.example.com/img/offer.jpg' }, suggestions: [ { type: 'url', text: 'Discover offer', url: { url: 'https://shop.example.com/offer' } } ] }, suggestions: [ { type: 'dial', text: 'Call us', dial: { phoneNumber: '+39037224525' } } ], fallbackSms: { sender: 'AGILE', text: 'Promo -20% on shop.example.com' } } }; fetch(`${baseUrl}/api/message-server/rcs/templates`, { method: 'POST', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => console.log(`Template created: ${data.id}`)); ``` ``` ```php 'Fall Promotion Card', 'description' => 'Vertical card with seasonal offer', 'type' => 'CARD', 'body' => [ 'cardOrientation' => 'VERTICAL', 'card' => [ 'title' => 'Fall promotion', 'description' => '20% off selected products', 'media' => [ 'height' => 'TALL', 'fileUrl' => 'https://cdn.example.com/img/offer.jpg' ], 'suggestions' => [ [ 'type' => 'url', 'text' => 'Discover offer', 'url' => ['url' => 'https://shop.example.com/offer'] ] ] ], 'suggestions' => [ [ 'type' => 'dial', 'text' => 'Call us', 'dial' => ['phoneNumber' => '+39037224525'] ] ], 'fallbackSms' => [ 'sender' => 'AGILE', 'text' => 'Promo -20% on shop.example.com' ] ] ]; $ch = curl_init("$baseUrl/api/message-server/rcs/templates"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $apiKey, 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); $response = curl_exec($ch); $template = json_decode($response, true); echo "Template created: " . $template['id']; ?> ``` ``` ```java public class CreateCardTemplate { public static void main(String[] args) throws Exception { String apiKey = "YOUR_API_KEY"; String baseUrl = "https://lora-api.agiletelecom.com"; JsonObject payload = new JsonObject(); payload.addProperty("name", "Fall Promotion Card"); payload.addProperty("description", "Vertical card with seasonal offer"); payload.addProperty("type", "CARD"); JsonObject body = new JsonObject(); body.addProperty("cardOrientation", "VERTICAL"); JsonObject card = new JsonObject(); card.addProperty("title", "Fall promotion"); card.addProperty("description", "20% off selected products"); JsonObject media = new JsonObject(); media.addProperty("height", "TALL"); media.addProperty("fileUrl", "https://cdn.example.com/img/offer.jpg"); card.add("media", media); JsonArray cardSuggestions = new JsonArray(); JsonObject urlSuggestion = new JsonObject(); urlSuggestion.addProperty("type", "url"); urlSuggestion.addProperty("text", "Discover offer"); JsonObject urlObj = new JsonObject(); urlObj.addProperty("url", "https://shop.example.com/offer"); urlSuggestion.add("url", urlObj); cardSuggestions.add(urlSuggestion); card.add("suggestions", cardSuggestions); body.add("card", card); JsonArray globalSuggestions = new JsonArray(); JsonObject dialSuggestion = new JsonObject(); dialSuggestion.addProperty("type", "dial"); dialSuggestion.addProperty("text", "Call us"); JsonObject dialObj = new JsonObject(); dialObj.addProperty("phoneNumber", "+39037224525"); dialSuggestion.add("dial", dialObj); globalSuggestions.add(dialSuggestion); body.add("suggestions", globalSuggestions); JsonObject fallbackSms = new JsonObject(); fallbackSms.addProperty("sender", "AGILE"); fallbackSms.addProperty("text", "Promo -20% on shop.example.com"); body.add("fallbackSms", fallbackSms); payload.add("body", body); HttpRequest request = HttpRequest.newBuilder() .uri(new java.net.URI(baseUrl + "/api/message-server/rcs/templates")) .header("X-Api-Key", apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload.toString())) .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); JsonObject result = JsonParser.parseString(response.body()).getAsJsonObject(); System.out.println("Template created: " + result.get("id")); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Text; using Newtonsoft.Json; public class CreateCardTemplate { public static async Task Main() { string apiKey = "YOUR_API_KEY"; string baseUrl = "https://lora-api.agiletelecom.com"; var payload = new { name = "Fall Promotion Card", description = "Vertical card with seasonal offer", type = "CARD", body = new { cardOrientation = "VERTICAL", card = new { title = "Fall promotion", description = "20% off selected products", media = new { height = "TALL", fileUrl = "https://cdn.example.com/img/offer.jpg" }, suggestions = new object[] { new { type = "url", text = "Discover offer", url = new { url = "https://shop.example.com/offer" } } } }, suggestions = new object[] { new { type = "dial", text = "Call us", dial = new { phoneNumber = "+39037224525" } } }, fallbackSms = new { sender = "AGILE", text = "Promo -20% on shop.example.com" } } }; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); var json = JsonConvert.SerializeObject(payload); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( $"{baseUrl}/api/message-server/rcs/templates", content ); var responseBody = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(responseBody); Console.WriteLine($"Template created: {result.id}"); } } } ``` ``` ```go package main "bytes" "encoding/json" "fmt" "io" "net/http" ) func main() { apiKey := "YOUR_API_KEY" baseUrl := "https://lora-api.agiletelecom.com" payload := map[string]interface{}{ "name": "Fall Promotion Card", "description": "Vertical card with seasonal offer", "type": "CARD", "body": map[string]interface{}{ "cardOrientation": "VERTICAL", "card": map[string]interface{}{ "title": "Fall promotion", "description": "20% off selected products", "media": map[string]string{ "height": "TALL", "fileUrl": "https://cdn.example.com/img/offer.jpg", }, "suggestions": []map[string]interface{}{ { "type": "url", "text": "Discover offer", "url": map[string]string{"url": "https://shop.example.com/offer"}, }, }, }, "suggestions": []map[string]interface{}{ { "type": "dial", "text": "Call us", "dial": map[string]string{"phoneNumber": "+39037224525"}, }, }, "fallbackSms": map[string]string{ "sender": "AGILE", "text": "Promo -20% on shop.example.com", }, }, } jsonData, _ := json.Marshal(payload) req, _ := http.NewRequest( "POST", fmt.Sprintf("%s/api/message-server/rcs/templates", baseUrl), bytes.NewBuffer(jsonData), ) req.Header.Set("X-Api-Key", apiKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result map[string]interface{} json.Unmarshal(body, &result) fmt.Printf("Template created: %v\n", result["id"]) } ``` ``` ```ruby require 'net/http' require 'json' api_key = 'YOUR_API_KEY' base_url = 'https://lora-api.agiletelecom.com' payload = { name: 'Fall Promotion Card', description: 'Vertical card with seasonal offer', type: 'CARD', body: { cardOrientation: 'VERTICAL', card: { title: 'Fall promotion', description: '20% off selected products', media: { height: 'TALL', fileUrl: 'https://cdn.example.com/img/offer.jpg' }, suggestions: [ { type: 'url', text: 'Discover offer', url: { url: 'https://shop.example.com/offer' } } ] }, suggestions: [ { type: 'dial', text: 'Call us', dial: { phoneNumber: '+39037224525' } } ], fallbackSms: { sender: 'AGILE', text: 'Promo -20% on shop.example.com' } } } uri = URI("#{base_url}/api/message-server/rcs/templates") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request['X-Api-Key'] = api_key request['Content-Type'] = 'application/json' request.body = payload.to_json response = http.request(request) result = JSON.parse(response.body) puts "Template created: #{result['id']}" ``` ``` ## CAROUSEL Template Display a scrollable horizontal list of 2-10 cards. Each card has its own media, content, and actions. Ideal for product catalogs, portfolios, and menus. ### Structure ```json { "type": "CAROUSEL", "body": { "cardWidth": "SMALL | MEDIUM | UNSPECIFIED", "cards": [ { "title": "Title", "description": "Description", "media": { "height": "SHORT | MEDIUM | TALL | UNSPECIFIED", "fileUrl": "https://..." }, "suggestions": [ ... ] } ], "suggestions": [ ... ], "fallbackSms": { ... } } } ``` | Field | Required | Notes | |-------|----------|-------| | `cardWidth` | Yes | Card size: `SMALL` or `MEDIUM` | | `cards` | Yes | Array of 2-10 card objects | | `cards[].media.height` | If media present | Height: `SHORT`, `MEDIUM`, `TALL` | | `cards[].suggestions` | No | Card-specific action buttons | | `suggestions` | No | Global actions shown below carousel | ### Create a CAROUSEL template This example creates a restaurant menu carousel with multiple dishes, photos, and booking actions. ``` ```bash curl -X POST https://lora-api.agiletelecom.com/api/message-server/rcs/templates \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Restaurant Menu Carousel", "description": "Scrollable menu with dishes and booking", "type": "CAROUSEL", "body": { "cardWidth": "MEDIUM", "cards": [ { "title": "Appetizers", "description": "A burst of flavors.", "media": { "height": "SHORT", "fileUrl": "https://cdn.example.com/appetizers.jpg" }, "suggestions": [ { "type": "dial", "text": "Call now", "dial": { "phoneNumber": "+39037224525" } } ] }, { "title": "Mains", "description": "Fresh pasta made locally.", "media": { "height": "SHORT", "fileUrl": "https://cdn.example.com/mains.jpg" }, "suggestions": [ { "type": "url", "text": "View online", "url": { "url": "https://restaurant.example.com/menu" } } ] } ], "suggestions": [ { "type": "calendar", "text": "Book a table", "calendar": { "title": "Dinner reservation", "description": "Reserve your table", "startTime": "2026-06-15T19:00:00Z", "endTime": "2026-06-15T21:00:00Z" } } ], "fallbackSms": { "sender": "AGILE", "text": "Discover our menu at restaurant.example.com" } } }' ``` ``` ```python api_key = "YOUR_API_KEY" base_url = "https://lora-api.agiletelecom.com" payload = { "name": "Restaurant Menu Carousel", "description": "Scrollable menu with dishes and booking", "type": "CAROUSEL", "body": { "cardWidth": "MEDIUM", "cards": [ { "title": "Appetizers", "description": "A burst of flavors.", "media": { "height": "SHORT", "fileUrl": "https://cdn.example.com/appetizers.jpg" }, "suggestions": [ { "type": "dial", "text": "Call now", "dial": {"phoneNumber": "+39037224525"} } ] }, { "title": "Mains", "description": "Fresh pasta made locally.", "media": { "height": "SHORT", "fileUrl": "https://cdn.example.com/mains.jpg" }, "suggestions": [ { "type": "url", "text": "View online", "url": {"url": "https://restaurant.example.com/menu"} } ] } ], "suggestions": [ { "type": "calendar", "text": "Book a table", "calendar": { "title": "Dinner reservation", "description": "Reserve your table", "startTime": "2026-06-15T19:00:00Z", "endTime": "2026-06-15T21:00:00Z" } } ], "fallbackSms": { "sender": "AGILE", "text": "Discover our menu at restaurant.example.com" } } } response = requests.post( f"{base_url}/api/message-server/rcs/templates", headers={"X-Api-Key": api_key}, json=payload ) template = response.json() print(f"Template created: {template['id']}") ``` ``` ```javascript const fetch = require('node-fetch'); const apiKey = 'YOUR_API_KEY'; const baseUrl = 'https://lora-api.agiletelecom.com'; const payload = { name: 'Restaurant Menu Carousel', description: 'Scrollable menu with dishes and booking', type: 'CAROUSEL', body: { cardWidth: 'MEDIUM', cards: [ { title: 'Appetizers', description: 'A burst of flavors.', media: { height: 'SHORT', fileUrl: 'https://cdn.example.com/appetizers.jpg' }, suggestions: [ { type: 'dial', text: 'Call now', dial: { phoneNumber: '+39037224525' } } ] }, { title: 'Mains', description: 'Fresh pasta made locally.', media: { height: 'SHORT', fileUrl: 'https://cdn.example.com/mains.jpg' }, suggestions: [ { type: 'url', text: 'View online', url: { url: 'https://restaurant.example.com/menu' } } ] } ], suggestions: [ { type: 'calendar', text: 'Book a table', calendar: { title: 'Dinner reservation', description: 'Reserve your table', startTime: '2026-06-15T19:00:00Z', endTime: '2026-06-15T21:00:00Z' } } ], fallbackSms: { sender: 'AGILE', text: 'Discover our menu at restaurant.example.com' } } }; fetch(`${baseUrl}/api/message-server/rcs/templates`, { method: 'POST', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => console.log(`Template created: ${data.id}`)); ``` ``` ```php 'Restaurant Menu Carousel', 'description' => 'Scrollable menu with dishes and booking', 'type' => 'CAROUSEL', 'body' => [ 'cardWidth' => 'MEDIUM', 'cards' => [ [ 'title' => 'Appetizers', 'description' => 'A burst of flavors.', 'media' => [ 'height' => 'SHORT', 'fileUrl' => 'https://cdn.example.com/appetizers.jpg' ], 'suggestions' => [ [ 'type' => 'dial', 'text' => 'Call now', 'dial' => ['phoneNumber' => '+39037224525'] ] ] ], [ 'title' => 'Mains', 'description' => 'Fresh pasta made locally.', 'media' => [ 'height' => 'SHORT', 'fileUrl' => 'https://cdn.example.com/mains.jpg' ], 'suggestions' => [ [ 'type' => 'url', 'text' => 'View online', 'url' => ['url' => 'https://restaurant.example.com/menu'] ] ] ] ], 'suggestions' => [ [ 'type' => 'calendar', 'text' => 'Book a table', 'calendar' => [ 'title' => 'Dinner reservation', 'description' => 'Reserve your table', 'startTime' => '2026-06-15T19:00:00Z', 'endTime' => '2026-06-15T21:00:00Z' ] ] ], 'fallbackSms' => [ 'sender' => 'AGILE', 'text' => 'Discover our menu at restaurant.example.com' ] ] ]; $ch = curl_init("$baseUrl/api/message-server/rcs/templates"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $apiKey, 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); $response = curl_exec($ch); $template = json_decode($response, true); echo "Template created: " . $template['id']; ?> ``` ``` ```java public class CreateCarouselTemplate { public static void main(String[] args) throws Exception { String apiKey = "YOUR_API_KEY"; String baseUrl = "https://lora-api.agiletelecom.com"; JsonObject payload = new JsonObject(); payload.addProperty("name", "Restaurant Menu Carousel"); payload.addProperty("description", "Scrollable menu with dishes and booking"); payload.addProperty("type", "CAROUSEL"); JsonObject body = new JsonObject(); body.addProperty("cardWidth", "MEDIUM"); JsonArray cards = new JsonArray(); // First card JsonObject card1 = new JsonObject(); card1.addProperty("title", "Appetizers"); card1.addProperty("description", "A burst of flavors."); JsonObject media1 = new JsonObject(); media1.addProperty("height", "SHORT"); media1.addProperty("fileUrl", "https://cdn.example.com/appetizers.jpg"); card1.add("media", media1); JsonArray card1Suggestions = new JsonArray(); JsonObject dialSugg = new JsonObject(); dialSugg.addProperty("type", "dial"); dialSugg.addProperty("text", "Call now"); JsonObject dialObj = new JsonObject(); dialObj.addProperty("phoneNumber", "+39037224525"); dialSugg.add("dial", dialObj); card1Suggestions.add(dialSugg); card1.add("suggestions", card1Suggestions); cards.add(card1); // Second card JsonObject card2 = new JsonObject(); card2.addProperty("title", "Mains"); card2.addProperty("description", "Fresh pasta made locally."); JsonObject media2 = new JsonObject(); media2.addProperty("height", "SHORT"); media2.addProperty("fileUrl", "https://cdn.example.com/mains.jpg"); card2.add("media", media2); JsonArray card2Suggestions = new JsonArray(); JsonObject urlSugg = new JsonObject(); urlSugg.addProperty("type", "url"); urlSugg.addProperty("text", "View online"); JsonObject urlObj = new JsonObject(); urlObj.addProperty("url", "https://restaurant.example.com/menu"); urlSugg.add("url", urlObj); card2Suggestions.add(urlSugg); card2.add("suggestions", card2Suggestions); cards.add(card2); body.add("cards", cards); JsonArray globalSuggestions = new JsonArray(); JsonObject calendarSugg = new JsonObject(); calendarSugg.addProperty("type", "calendar"); calendarSugg.addProperty("text", "Book a table"); JsonObject calendar = new JsonObject(); calendar.addProperty("title", "Dinner reservation"); calendar.addProperty("description", "Reserve your table"); calendar.addProperty("startTime", "2026-06-15T19:00:00Z"); calendar.addProperty("endTime", "2026-06-15T21:00:00Z"); calendarSugg.add("calendar", calendar); globalSuggestions.add(calendarSugg); body.add("suggestions", globalSuggestions); JsonObject fallbackSms = new JsonObject(); fallbackSms.addProperty("sender", "AGILE"); fallbackSms.addProperty("text", "Discover our menu at restaurant.example.com"); body.add("fallbackSms", fallbackSms); payload.add("body", body); HttpRequest request = HttpRequest.newBuilder() .uri(new java.net.URI(baseUrl + "/api/message-server/rcs/templates")) .header("X-Api-Key", apiKey) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload.toString())) .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); JsonObject result = JsonParser.parseString(response.body()).getAsJsonObject(); System.out.println("Template created: " + result.get("id")); } } ``` ``` ```csharp using System; using System.Net.Http; using System.Text; using Newtonsoft.Json; public class CreateCarouselTemplate { public static async Task Main() { string apiKey = "YOUR_API_KEY"; string baseUrl = "https://lora-api.agiletelecom.com"; var payload = new { name = "Restaurant Menu Carousel", description = "Scrollable menu with dishes and booking", type = "CAROUSEL", body = new { cardWidth = "MEDIUM", cards = new object[] { new { title = "Appetizers", description = "A burst of flavors.", media = new { height = "SHORT", fileUrl = "https://cdn.example.com/appetizers.jpg" }, suggestions = new object[] { new { type = "dial", text = "Call now", dial = new { phoneNumber = "+39037224525" } } } }, new { title = "Mains", description = "Fresh pasta made locally.", media = new { height = "SHORT", fileUrl = "https://cdn.example.com/mains.jpg" }, suggestions = new object[] { new { type = "url", text = "View online", url = new { url = "https://restaurant.example.com/menu" } } } } }, suggestions = new object[] { new { type = "calendar", text = "Book a table", calendar = new { title = "Dinner reservation", description = "Reserve your table", startTime = "2026-06-15T19:00:00Z", endTime = "2026-06-15T21:00:00Z" } } }, fallbackSms = new { sender = "AGILE", text = "Discover our menu at restaurant.example.com" } } }; using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("X-Api-Key", apiKey); var json = JsonConvert.SerializeObject(payload); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync( $"{baseUrl}/api/message-server/rcs/templates", content ); var responseBody = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(responseBody); Console.WriteLine($"Template created: {result.id}"); } } } ``` ``` ```go package main "bytes" "encoding/json" "fmt" "io" "net/http" ) func main() { apiKey := "YOUR_API_KEY" baseUrl := "https://lora-api.agiletelecom.com" payload := map[string]interface{}{ "name": "Restaurant Menu Carousel", "description": "Scrollable menu with dishes and booking", "type": "CAROUSEL", "body": map[string]interface{}{ "cardWidth": "MEDIUM", "cards": []map[string]interface{}{ { "title": "Appetizers", "description": "A burst of flavors.", "media": map[string]string{ "height": "SHORT", "fileUrl": "https://cdn.example.com/appetizers.jpg", }, "suggestions": []map[string]interface{}{ { "type": "dial", "text": "Call now", "dial": map[string]string{"phoneNumber": "+39037224525"}, }, }, }, { "title": "Mains", "description": "Fresh pasta made locally.", "media": map[string]string{ "height": "SHORT", "fileUrl": "https://cdn.example.com/mains.jpg", }, "suggestions": []map[string]interface{}{ { "type": "url", "text": "View online", "url": map[string]string{"url": "https://restaurant.example.com/menu"}, }, }, }, }, "suggestions": []map[string]interface{}{ { "type": "calendar", "text": "Book a table", "calendar": map[string]string{ "title": "Dinner reservation", "description": "Reserve your table", "startTime": "2026-06-15T19:00:00Z", "endTime": "2026-06-15T21:00:00Z", }, }, }, "fallbackSms": map[string]string{ "sender": "AGILE", "text": "Discover our menu at restaurant.example.com", }, }, } jsonData, _ := json.Marshal(payload) req, _ := http.NewRequest( "POST", fmt.Sprintf("%s/api/message-server/rcs/templates", baseUrl), bytes.NewBuffer(jsonData), ) req.Header.Set("X-Api-Key", apiKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result map[string]interface{} json.Unmarshal(body, &result) fmt.Printf("Template created: %v\n", result["id"]) } ``` ``` ```ruby require 'net/http' require 'json' api_key = 'YOUR_API_KEY' base_url = 'https://lora-api.agiletelecom.com' payload = { name: 'Restaurant Menu Carousel', description: 'Scrollable menu with dishes and booking', type: 'CAROUSEL', body: { cardWidth: 'MEDIUM', cards: [ { title: 'Appetizers', description: 'A burst of flavors.', media: { height: 'SHORT', fileUrl: 'https://cdn.example.com/appetizers.jpg' }, suggestions: [ { type: 'dial', text: 'Call now', dial: { phoneNumber: '+39037224525' } } ] }, { title: 'Mains', description: 'Fresh pasta made locally.', media: { height: 'SHORT', fileUrl: 'https://cdn.example.com/mains.jpg' }, suggestions: [ { type: 'url', text: 'View online', url: { url: 'https://restaurant.example.com/menu' } } ] } ], suggestions: [ { type: 'calendar', text: 'Book a table', calendar: { title: 'Dinner reservation', description: 'Reserve your table', startTime: '2026-06-15T19:00:00Z', endTime: '2026-06-15T21:00:00Z' } } ], fallbackSms: { sender: 'AGILE', text: 'Discover our menu at restaurant.example.com' } } } uri = URI("#{base_url}/api/message-server/rcs/templates") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.path) request['X-Api-Key'] = api_key request['Content-Type'] = 'application/json' request.body = payload.to_json response = http.request(request) result = JSON.parse(response.body) puts "Template created: #{result['id']}" ``` ``` ## Suggestion types and required fields | Type | Description | Required Fields | Example | |------|-------------|-----------------|---------| | `reply` | Text reply button | - | `{ "type": "reply", "text": "Yes", "reply": {} }` | | `url` | Open a web link | url.url | `{ "type": "url", "text": "Learn more", "url": {"url": "https://example.com"} }` | | `dial` | Initiate a phone call | dial.phoneNumber | `{ "type": "dial", "text": "Call us", "dial": {"phoneNumber": "+39037224525"} }` | | `coordinates` | Navigate to a location | latitude, longitude | `{ "type": "coordinates", "text": "Find us", "locationCoordinates": {"label": "Store", "latitude": 45.464, "longitude": 9.188} }` | | `location` | Search for nearby places | locationQuery.query | `{ "type": "location", "text": "Nearby", "locationQuery": {"query": "restaurants"} }` | | `calendar` | Add event to calendar | startTime, endTime | `{ "type": "calendar", "text": "Save date", "calendar": {"title": "Webinar", "startTime": "2026-06-15T14:00:00Z", "endTime": "2026-06-15T15:00:00Z"} }` | :::info Dates and times Always use ISO-8601 format: `2026-06-15T14:00:00Z` (UTC) or `2026-06-15T14:00:00+02:00` (with timezone offset). ::: ## Media in cards Add images or videos to cards with the media object: | Field | Type | Notes | |-------|------|-------| | `height` | enum | Required if media is present. Options: `SHORT` (50px), `MEDIUM` (100px), `TALL` (200px), `UNSPECIFIED` | | `fileUrl` | string | Public HTTPS URL to image or video file | :::warning Media hosting requirements - `fileUrl` must be publicly accessible (no authentication) - Keep files online during the entire campaign send - Missing or unreachable media causes send errors - Use CDN or stable hosting for reliability ::: ## Enumerations reference **Template and card types:** - **type**: `TEXT` | `CARD` | `CAROUSEL` - **cardOrientation**: `VERTICAL` (recommended) | `HORIZONTAL` | `UNSPECIFIED` - **thumbnailAlignment**: `LEFT` | `RIGHT` | `UNSPECIFIED` **Media dimensions:** - **height**: `SHORT` | `MEDIUM` | `TALL` | `UNSPECIFIED` - **cardWidth**: `SMALL` | `MEDIUM` | `UNSPECIFIED` ## Placeholders for personalization Personalize templates with dynamic values using placeholders. Wrap variable names in curly braces (e.g., `{firstName}`, `{orderId}`) and provide values during send. ### How placeholders work 1. Define placeholders in your template text 2. Send a message referencing the template 3. Provide placeholder values in the `placeholders` object 4. Substitution happens automatically ### Placeholder example **Template definition:** ```json { "name": "Order Status", "type": "TEXT", "body": { "text": "Hi {firstName}, your order #{orderId} is being prepared. Estimated delivery: {deliveryDate}." } } ``` **Send with values:** ```json { "templateId": "tpl_123", "destination": "+391234567890", "agentId": "agent_abc", "messageId": "msg-001", "placeholders": { "firstName": "Marco", "orderId": "12345", "deliveryDate": "June 15" } } ``` **Recipient sees:** ``` Hi Marco, your order #12345 is being prepared. Estimated delivery: June 15. ``` :::warning Missing placeholder values If a placeholder is defined in the template but not provided during send, the unreplaced placeholder (e.g., `{firstName}`) will appear in the message. Always provide all placeholder values. ::: ## Common template errors and how to fix them | Template Type | Error | Solution | |---------------|-------|----------| | TEXT | Missing `body.text` | Ensure `text` field is populated and non-empty | | CARD | Missing `body.card` or media height | Add `card` object and set `media.height` if including media | | CAROUSEL | Missing `cardWidth` or empty `cards` | Set `cardWidth` and include 2-10 cards in array | | Media | `fileUrl` not publicly accessible | Use HTTPS URLs; verify no authentication required | | Suggestions | Type-field mismatch | Match suggestion type with required fields (e.g., `dial` needs `phoneNumber`) | | Placeholders | Values not provided at send time | Always supply placeholder values to avoid `{placeholder}` appearing in message | ## Best practices checklist - **Placeholders**: Provide all placeholder values during send to avoid unreplaced text - **Media URLs**: Use stable, public HTTPS URLs; keep them online throughout the campaign - **Media height**: Always specify `SHORT`, `MEDIUM`, or `TALL` for consistent rendering - **Suggestions**: Use consistent, concise button labels (under 25 characters) - **SMS fallback**: If enabled, ensure sender ID is authorized and matches template language - **Testing**: Test with real placeholder values before sending campaigns - **Card orientation**: Default to `VERTICAL` for optimal mobile display ## Next steps Once you create templates, use the RCS Send API to deliver messages: 1. **Send from template**: Reference template ID with placeholder values 2. **Send inline**: Create ad-hoc messages without storing templates 3. **Track webhooks**: Monitor delivery status and user interactions See [RCS API documentation](./rcs.md) for send endpoints, authentication, and webhook setup. To test templates interactively, download the Postman Collection from your dashboard.