Signals /Messaging

Auto Failover API

This API enables businesses to send a single message that is automatically cascaded across multiple channels — WhatsApp, SMS, Email and Voice — based on a failover profile you created in your Termii dashboard. If the first channel fails to deliver, the next is attempted; if that fails, the next; and so on until one succeeds or the cascade is exhausted.

You create the profile once (channel priority, templates, variables, retry windows) and reference it from every send with its profileId. One API call. One payload of variables. The cascade logic lives in the profile, not in your code.

Routing

When the cascade reaches the SMS leg, the routing parameter decides which SMS route the message takes. Accepted values:

RoutingDescription
genericUsed to send promotional messages and messages to phone numbers not on DND (Do Not Disturb).
DNDDelivers messages to all phone numbers, regardless of DND restriction. Ideal for transactional or critical messages (OTPs, order updates, security alerts).
Routing only affects the SMS leg of the cascade. WhatsApp, Email and Voice channels follow their own routing rules defined on the profile and ignore this parameter.

Send via Auto Failover

Triggers the cascade defined on a failover profile. Termii queues the first-priority channel immediately, and automatically falls through to each subsequent channel if the previous attempt fails or times out. The response returns a request_id you can use to track the cascade's progress via webhooks or the Status endpoint.

POST https://BASE_URL/api/v1/auto-route/send

Body params

OptionsRequiredDescription
api_keyyesstring
Your API key (it can be found on your Termii dashboard).
profileIdyesstring
The ID of the failover profile to use. Profiles are created and managed from your Termii dashboard under Auto Failover → Configurations.
(Example: prof_otp_login_a3f9)
recipientyesobject
The destination contact details. Must include at least one address matching a channel enabled on the profile. Supported keys: phone and email.
recipient.phoneconditionalstring
Phone number in international format. Required if the profile enables any of the SMS, WhatsApp or Voice channels.
(Example: +2348031234567)
recipient.emailconditionalstring
Email address. Required if the profile enables the Email channel.
(Example: ada@example.com)
variablesyesobject
Key-value pairs matching the variables defined on your failover profile. The same object is used to render every channel's template — you define the variables once per send, and Termii substitutes the right value into each channel's message.

Variable names must exactly match the names on the profile. Missing variables cause the send to be rejected with a 422. See the error-response examples below.
routingyesstring
Specifies the SMS route when the cascade attempts the SMS channel. Accepted values:
  • DND – for transactional or critical messages (bypasses DND restrictions).
  • generic – for promotional or non-transactional messages.

How variables work across channels
Your profile defines a single variable schema (e.g. code, name) shared by every channel's template. If your WhatsApp template reads Your code is {{code}} and your SMS template reads {{code}} is your login code, both are rendered from the same code field in this request.
Disclaimer (for Voice)
If a channel template contains a verification code, add spaces between the digits (e.g. "4 8 2 9 1 3" instead of "482913"). This ensures the Voice engine reads the code digit-by-digit instead of as a continuous number.

{
    "api_key": "Your API Key",
    "profileId": "prof_otp_login_a3f9",
    "recipient": {
        "phone": "+2348031234567",
        "email": "ada@example.com"
    },
    "variables": {
        "code": "482913",
        "name": "Ada"
    },
    "routing": "DND"
}
// Browser / modern JS
const response = await fetch("https://BASE_URL/api/v1/auto-route/send", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
        api_key: "YOUR_API_KEY",
        profileId: "prof_otp_login_a3f9",
        recipient: {
            phone: "+2348031234567",
            email: "ada@example.com"
        },
        variables: {
            code: "482913",
            name: "Ada"
        },
        routing: "DND"
    })
});

const data = await response.json();
console.log(data);
const axios = require("axios");

const url = "https://BASE_URL/api/v1/auto-route/send";

const payload = {
    api_key: "YOUR_API_KEY",
    profileId: "prof_otp_login_a3f9",
    recipient: {
        phone: "+2348031234567",
        email: "ada@example.com"
    },
    variables: {
        code: "482913",
        name: "Ada"
    },
    routing: "DND"
};

axios.post(url, payload)
    .then(res => console.log(res.data))
    .catch(err => console.error(err.response?.data || err));
import requests

url = "https://BASE_URL/api/v1/auto-route/send"

payload = {
    "api_key": "YOUR_API_KEY",
    "profileId": "prof_otp_login_a3f9",
    "recipient": {
        "phone": "+2348031234567",
        "email": "ada@example.com"
    },
    "variables": {
        "code": "482913",
        "name": "Ada"
    },
    "routing": "DND"
}

response = requests.post(url, json=payload)
print(response.json())
using System.Net.Http;
using System.Net.Http.Json;

var client = new HttpClient();

var payload = new {
    api_key   = "YOUR_API_KEY",
    profileId = "prof_otp_login_a3f9",
    recipient = new {
        phone = "+2348031234567",
        email = "ada@example.com"
    },
    variables = new {
        code = "482913",
        name = "Ada"
    },
    routing   = "DND"
};

var response = await client.PostAsJsonAsync(
    "https://BASE_URL/api/v1/auto-route/send", payload);

var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.get("application/json");

String json = """
    {
      "api_key": "YOUR_API_KEY",
      "profileId": "prof_otp_login_a3f9",
      "recipient": {
        "phone": "+2348031234567",
        "email": "ada@example.com"
      },
      "variables": {
        "code": "482913",
        "name": "Ada"
      },
      "routing": "DND"
    }
    """;

RequestBody body = RequestBody.create(json, JSON);

Request request = new Request.Builder()
    .url("https://BASE_URL/api/v1/auto-route/send")
    .post(body)
    .build();

try (Response response = client.newCall(request).execute()) {
    System.out.println(response.body().string());
}
<?php

$payload = [
    "api_key"   => "YOUR_API_KEY",
    "profileId" => "prof_otp_login_a3f9",
    "recipient" => [
        "phone" => "+2348031234567",
        "email" => "ada@example.com"
    ],
    "variables" => [
        "code" => "482913",
        "name" => "Ada"
    ],
    "routing"   => "DND"
];

$ch = curl_init("https://BASE_URL/api/v1/auto-route/send");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

echo $response;

Sample Response - 200 OK

The response returns immediately with a request_id; the cascade runs asynchronously from here. Use the request_id to look up the final cascade outcome and per-channel attempt detail via the Status endpoint, or configure webhooks on your dashboard to receive updates as each channel completes.

{
    "success": true,
    "message": "Created",
    "data": {
        "chainId": "ar_13c350b949114f8484b2edbbc11d65a3",
        "messageId": "sig_6457d280e85746d789dd96893fd53540",
        "channel": "sms",
        "status": "submitted",
        "message": "Message accepted for delivery",
        "acceptedAt": "2026-10-14T16:35:49.257240634+01:00"
    },
    "timestamp": "2026-10-14T16:35:49.257266864+01:00"
}

Sample Response - 422 Unprocessable Entity

Returned when the request references a variable that is not defined on the profile, or omits a variable the profile's templates require.

{
    "code": "variables_mismatch",
    "message": "Required variables missing from request",
    "missing": ["expiry_minutes"],
    "unexpected": []
}

Sample Response - 404 Not Found

Returned when the profileId does not match any failover profile on the workspace.

{
    "code": "profile_not_found",
    "message": "No failover profile matches the provided profileId",
    "profileId": "prof_otp_login_a3f9"
}
Updated at, Monday, October 19, 2026