curl --request POST \
--url https://api.orbit.devotel.io/api/v1/messages/sms \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "<string>",
"body": "<string>",
"webhook_url": "<string>",
"metadata": {},
"test": true,
"validity_period": "<string>",
"max_price": 50,
"fallback_from": "<string>",
"from": "<string>",
"contact_id": "<string>",
"media_url": "<string>",
"media_urls": [
"<string>"
],
"scheduled_at": "<string>",
"shorten_urls": true,
"track_clicks": true,
"flash_sms": true
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/messages/sms"
payload = {
"to": "<string>",
"body": "<string>",
"webhook_url": "<string>",
"metadata": {},
"test": True,
"validity_period": "<string>",
"max_price": 50,
"fallback_from": "<string>",
"from": "<string>",
"contact_id": "<string>",
"media_url": "<string>",
"media_urls": ["<string>"],
"scheduled_at": "<string>",
"shorten_urls": True,
"track_clicks": True,
"flash_sms": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '<string>',
body: JSON.stringify('<string>'),
webhook_url: '<string>',
metadata: {},
test: true,
validity_period: '<string>',
max_price: 50,
fallback_from: '<string>',
from: '<string>',
contact_id: '<string>',
media_url: '<string>',
media_urls: ['<string>'],
scheduled_at: '<string>',
shorten_urls: true,
track_clicks: true,
flash_sms: true
})
};
fetch('https://api.orbit.devotel.io/api/v1/messages/sms', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/messages/sms",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'body' => '<string>',
'webhook_url' => '<string>',
'metadata' => [
],
'test' => true,
'validity_period' => '<string>',
'max_price' => 50,
'fallback_from' => '<string>',
'from' => '<string>',
'contact_id' => '<string>',
'media_url' => '<string>',
'media_urls' => [
'<string>'
],
'scheduled_at' => '<string>',
'shorten_urls' => true,
'track_clicks' => true,
'flash_sms' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/messages/sms"
payload := strings.NewReader("{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/messages/sms")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/messages/sms")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"status": "<string>",
"channel": "sms",
"from": "<string>",
"to": "<string>",
"body": "<string>",
"segments": 123,
"price": "<string>",
"currency": "<string>",
"external_id": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "<string>",
"status": 429,
"retry_after": 2
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}Send an SMS
Send an SMS (or caption-less MMS when media_url/media_urls is set) to an E.164 phone number.
curl --request POST \
--url https://api.orbit.devotel.io/api/v1/messages/sms \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"to": "<string>",
"body": "<string>",
"webhook_url": "<string>",
"metadata": {},
"test": true,
"validity_period": "<string>",
"max_price": 50,
"fallback_from": "<string>",
"from": "<string>",
"contact_id": "<string>",
"media_url": "<string>",
"media_urls": [
"<string>"
],
"scheduled_at": "<string>",
"shorten_urls": true,
"track_clicks": true,
"flash_sms": true
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/messages/sms"
payload = {
"to": "<string>",
"body": "<string>",
"webhook_url": "<string>",
"metadata": {},
"test": True,
"validity_period": "<string>",
"max_price": 50,
"fallback_from": "<string>",
"from": "<string>",
"contact_id": "<string>",
"media_url": "<string>",
"media_urls": ["<string>"],
"scheduled_at": "<string>",
"shorten_urls": True,
"track_clicks": True,
"flash_sms": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
to: '<string>',
body: JSON.stringify('<string>'),
webhook_url: '<string>',
metadata: {},
test: true,
validity_period: '<string>',
max_price: 50,
fallback_from: '<string>',
from: '<string>',
contact_id: '<string>',
media_url: '<string>',
media_urls: ['<string>'],
scheduled_at: '<string>',
shorten_urls: true,
track_clicks: true,
flash_sms: true
})
};
fetch('https://api.orbit.devotel.io/api/v1/messages/sms', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/messages/sms",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'to' => '<string>',
'body' => '<string>',
'webhook_url' => '<string>',
'metadata' => [
],
'test' => true,
'validity_period' => '<string>',
'max_price' => 50,
'fallback_from' => '<string>',
'from' => '<string>',
'contact_id' => '<string>',
'media_url' => '<string>',
'media_urls' => [
'<string>'
],
'scheduled_at' => '<string>',
'shorten_urls' => true,
'track_clicks' => true,
'flash_sms' => true
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/messages/sms"
payload := strings.NewReader("{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/messages/sms")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/messages/sms")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"to\": \"<string>\",\n \"body\": \"<string>\",\n \"webhook_url\": \"<string>\",\n \"metadata\": {},\n \"test\": true,\n \"validity_period\": \"<string>\",\n \"max_price\": 50,\n \"fallback_from\": \"<string>\",\n \"from\": \"<string>\",\n \"contact_id\": \"<string>\",\n \"media_url\": \"<string>\",\n \"media_urls\": [\n \"<string>\"\n ],\n \"scheduled_at\": \"<string>\",\n \"shorten_urls\": true,\n \"track_clicks\": true,\n \"flash_sms\": true\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"status": "<string>",
"channel": "sms",
"from": "<string>",
"to": "<string>",
"body": "<string>",
"segments": 123,
"price": "<string>",
"currency": "<string>",
"external_id": "<string>",
"created_at": "2023-11-07T05:31:56Z"
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "RATE_LIMITED",
"message": "<string>",
"status": 429,
"retry_after": 2
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"status": 123,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"docs_url": "<string>"
}
}Authorizations
Dashboard JWT token from Clerk
Headers
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the Idempotency-Replay: true response header.
1 - 255Sandbox opt-in for Clerk-session-authenticated requests. Set to true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.
true, false Body
Phone number. Accepts E.164 (preferred) or display formats and PBX-extension suffixes (x1234, ;ext=1234, ,ext=1234); normalized server-side to E.164 before send.
1 - 64SMS body text. Optional when media_url or media_urls is set (caption-less MMS).
1600DLR callback URL (HTTPS). Optional — an empty string is treated as 'no webhook'. Non-empty values are validated server-side: must be https:// and pass the SSRF guard.
2048Custom key-value metadata
When true, marks this send as a throwaway test. The provider call still happens and billing still runs, but the recipient is NOT auto-upserted into the contacts table. Use this for dashboard 'Send Test SMS' flows to avoid polluting the operator's contacts list with verification numbers. TEST-USER P1 / DECISION — TEST_SMS_NO_CONTACT_CREATE_2026_05_28.
Twilio ValidityPeriod parity. Carrier-level expiry hint after which the SMSC drops the attempt. Suffixed grammar: s (seconds, default), m (minutes), h (hours), d (days). Examples: '30m', '1h', '24h', '2d'. Consumed by Telnyx (valid_until), Jasmin (validity-period), and SMPP (validity_period hours). GAP2_CPaaS_33.
^[1-9][0-9]*[smhd]?$Twilio MaxPrice parity. Hard per-message USD ceiling. The send is rejected 422 MAX_PRICE_EXCEEDED if the resolved per-unit cost exceeds this cap — useful as an anti-fraud guard on international SMS to premium-rate destinations. Capped at 100 USD per message to defend against units mistakes. GAP2_CPaaS_33.
0 < x <= 100Twilio FallbackFrom parity. An optional secondary sender identity recorded on the message as metadata.fallback_from for audit and record-keeping. Same allowed shape as from (E.164 / alphanumeric sender ID / __shared_generic__). Audit-only: the value is stored on the message record and is not currently auto-swapped in on a rejected attempt, so treat it as a record of intent rather than an active carrier-failover retry.
1 - 64Sender identity: E.164 phone number, alphanumeric sender ID (1-11 characters with letters + spaces), short code (3-8 digits), or the platform sentinel __shared_generic__. Normalized and validated server-side.
1 - 64Optional reference to a contact in the tenant's contacts table. When provided, the API resolves the contact's phone and uses it as the recipient, overriding the to field. Used by the dashboard inbox when the operator has only the display-masked contact value.
100Media attachment URL (HTTPS). Auto-upgrades SMS to MMS when present. Validated server-side for HTTPS scheme and SSRF safety.
Array of media attachment URLs (up to 10). Auto-upgrades SMS to MMS. Each URL validated server-side for HTTPS scheme and SSRF safety.
10ISO 8601 datetime for scheduled delivery. Must be in the future and within the tenant's schedule window. Accepts both full ISO 8601 strings (e.g., '2026-04-16T14:30:00Z') and datetime-local values (e.g., '2026-04-16T14:30'). Validated server-side.
When true, auto-shorten any URLs in the message body using the tenant's configured link shortener. Link-tracking metadata is preserved.
When true, track clicks on shortened links via redirect analytics. Only applies when shorten_urls is true.
When true, send as a Flash SMS (Class 0) that displays immediately on the recipient device without saving to the inbox. Not supported by all carriers.