curl --request POST \
--url https://api.orbit.devotel.io/api/v1/notify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"bindings": [
{
"address": "<string>",
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"max_price": 50
}
],
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"fallback_window_seconds": 43215,
"max_price": 50,
"max_total_price": 500
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/notify"
payload = {
"bindings": [
{
"address": "<string>",
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"max_price": 50
}
],
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"fallback_window_seconds": 43215,
"max_price": 50,
"max_total_price": 500
}
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({
bindings: [
{
address: '<string>',
body: JSON.stringify('<string>'),
from: '<string>',
template_name: '<string>',
template_params: {},
metadata: {},
max_price: 50
}
],
body: JSON.stringify('<string>'),
from: '<string>',
template_name: '<string>',
template_params: {},
metadata: {},
fallback_window_seconds: 43215,
max_price: 50,
max_total_price: 500
})
};
fetch('https://api.orbit.devotel.io/api/v1/notify', 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/notify",
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([
'bindings' => [
[
'address' => '<string>',
'body' => '<string>',
'from' => '<string>',
'template_name' => '<string>',
'template_params' => [
],
'metadata' => [
],
'max_price' => 50
]
],
'body' => '<string>',
'from' => '<string>',
'template_name' => '<string>',
'template_params' => [
],
'metadata' => [
],
'fallback_window_seconds' => 43215,
'max_price' => 50,
'max_total_price' => 500
]),
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/notify"
payload := strings.NewReader("{\n \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\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/notify")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/notify")
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 \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\n}"
response = http.request(request)
puts response.read_body{
"data": {
"notify_id": "<string>",
"mode": "fanout",
"summary": {
"total": 123,
"succeeded": 123,
"failed": 123
},
"results": [
{
"index": 123,
"channel": "<string>",
"to": "<string>",
"status": "queued",
"message_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
}
}
]
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"data": {
"notify_id": "<string>",
"mode": "fanout",
"summary": {
"total": 123,
"succeeded": 123,
"failed": 123
},
"results": [
{
"index": 123,
"channel": "<string>",
"to": "<string>",
"status": "queued",
"message_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
}
}
]
},
"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": 422,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"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>"
}
}Notify — multi-channel fan-out or waterfall
Send a single payload to multiple recipients across heterogeneous channels (SMS, WhatsApp, email, RCS, push, etc.) in one API call. Each recipient is addressed by channel and destination (e.g., phone number or email). Supports two delivery strategies: fanout (send every binding immediately, returning per-binding delivery handles) and waterfall (send the richest binding first, then auto-escalate down the ordered bindings asynchronously when the active channel reports undelivered within the TTL). Returns HTTP 200 when every binding succeeds (fanout) or at least one binding queues (waterfall); 207 Multi-Status when at least one binding failed AND at least one succeeded (fanout only); 422 when every binding failed (fanout) or every binding rejected synchronously (waterfall).
curl --request POST \
--url https://api.orbit.devotel.io/api/v1/notify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"bindings": [
{
"address": "<string>",
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"max_price": 50
}
],
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"fallback_window_seconds": 43215,
"max_price": 50,
"max_total_price": 500
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/notify"
payload = {
"bindings": [
{
"address": "<string>",
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"max_price": 50
}
],
"body": "<string>",
"from": "<string>",
"template_name": "<string>",
"template_params": {},
"metadata": {},
"fallback_window_seconds": 43215,
"max_price": 50,
"max_total_price": 500
}
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({
bindings: [
{
address: '<string>',
body: JSON.stringify('<string>'),
from: '<string>',
template_name: '<string>',
template_params: {},
metadata: {},
max_price: 50
}
],
body: JSON.stringify('<string>'),
from: '<string>',
template_name: '<string>',
template_params: {},
metadata: {},
fallback_window_seconds: 43215,
max_price: 50,
max_total_price: 500
})
};
fetch('https://api.orbit.devotel.io/api/v1/notify', 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/notify",
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([
'bindings' => [
[
'address' => '<string>',
'body' => '<string>',
'from' => '<string>',
'template_name' => '<string>',
'template_params' => [
],
'metadata' => [
],
'max_price' => 50
]
],
'body' => '<string>',
'from' => '<string>',
'template_name' => '<string>',
'template_params' => [
],
'metadata' => [
],
'fallback_window_seconds' => 43215,
'max_price' => 50,
'max_total_price' => 500
]),
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/notify"
payload := strings.NewReader("{\n \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\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/notify")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/notify")
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 \"bindings\": [\n {\n \"address\": \"<string>\",\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"max_price\": 50\n }\n ],\n \"body\": \"<string>\",\n \"from\": \"<string>\",\n \"template_name\": \"<string>\",\n \"template_params\": {},\n \"metadata\": {},\n \"fallback_window_seconds\": 43215,\n \"max_price\": 50,\n \"max_total_price\": 500\n}"
response = http.request(request)
puts response.read_body{
"data": {
"notify_id": "<string>",
"mode": "fanout",
"summary": {
"total": 123,
"succeeded": 123,
"failed": 123
},
"results": [
{
"index": 123,
"channel": "<string>",
"to": "<string>",
"status": "queued",
"message_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
}
}
]
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"data": {
"notify_id": "<string>",
"mode": "fanout",
"summary": {
"total": 123,
"succeeded": 123,
"failed": 123
},
"results": [
{
"index": 123,
"channel": "<string>",
"to": "<string>",
"status": "queued",
"message_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
}
}
]
},
"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": 422,
"details": {}
},
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"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
Array of per-binding payloads. Each specifies a channel and destination. Minimum 2 bindings for waterfall mode.
1 - 10000 elementsShow child attributes
Show child attributes
Shared message body — applied to every binding that doesn't carry its own.
1 - 4000Shared sender identifier — applied to every binding that doesn't carry its own.
1 - 128Shared template name — applied when neither shared nor per-binding body is set.
1 - 128Shared template parameters — applied to every binding that doesn't carry its own.
Show child attributes
Show child attributes
Shared metadata bag — merged onto every binding's metadata.
Show child attributes
Show child attributes
Delivery strategy. fanout (default): send every binding immediately. waterfall: send the first binding; auto-escalate remaining bindings on DLR undelivered within the TTL.
fanout, waterfall Waterfall freshness window in seconds. An undelivered DLR landing after this window does NOT escalate. Ignored in fanout mode. Default 24h; floor 30s, ceiling 24h.
30 <= x <= 86400Shared per-step cost ceiling in USD, applied to every binding that doesn't carry its own max_price. In waterfall mode this caps EACH hop independently (not the cumulative spend across hops).
0 < x <= 100Cumulative USD cost cap across the ENTIRE waterfall cascade (the primary send plus every escalated fallback hop). Unlike max_price, this bounds the TOTAL spend of chasing one recipient across every channel — hops that would push the running total over this cap are dropped from the escalation tail (never attempted) rather than the send being rejected. Hop order is never changed, only trimmed. Ignored in fanout mode.
0 < x <= 1000Response
Success — every binding queued (fanout) or at least one binding queued (waterfall). Returns per-binding delivery handles and async escalation chain.