Send RCS message
Fire a one-off RCS message to one recipient. For bulk sends use Broadcasts. This endpoint is for the programmatic side (order shipped, OTP, support reply).
One endpoint, two body shapes. Presence of template_id in the
body picks the send path:
POST /api/v1/rcs/messages/send
│
├── body has template_id → stored-template path
│ (loads your approved template, fills
│ its variables, sends)
│
└── body has message → composed path
(sends the content you built in
the body)- Auth:
Authorization: Bearer <token>, a session JWT or an API key with themessages:sendscope. See Authentication. - Operators and owners can send; viewers get
403 not_operator. - Billing: prepaid balance is checked before the send, and the message is charged once the network accepts it. A message the network refuses is not charged; one that fails after it was accepted is refunded automatically.
There is no /api/v1/rcs/send
Earlier versions of this page named POST /api/v1/rcs/send and a
/rcs/send-template alias. Neither exists on the RCS Panel API. Use
POST /api/v1/rcs/messages/send for both shapes.
Picking a bot (multi-bot accounts)
Pass bot_id in the body to choose which bot sends:
{
"to": "+919876543210",
"bot_id": "9c1b7f4e-2a11-4c8e-8e0e-3f5d1b2c9e01",
"message": { "type": "text", "text": "..." }
}- With
template_id,bot_idis optional: the template's own bot sends it. If you do pass one and it is a different bot, the request is refused with400 template_bot_mismatch. - With
message, leavingbot_idout uses your default bot.
The bot must be active, or you get 403 with code = "bot_not_active".
Team members can only send from bots assigned to them.
Prerequisites
- An active bot. Any active bot works here, whatever it is registered for. Unlike the broadcast endpoints, this one is not restricted by the bot's carrier registration, a Transactional or OTP bot can use it without limitation, and it is the endpoint those bots are meant to be driven through. See What your bot is registered to send.
- A Promotional bot can only send between 7:00am and 10:00pm IST. Outside
that window you get
403 outside_promotional_window. - Enough wallet balance for one message (or postpaid billing).
Shape 1, Stored template (recommended)
For messages that fit a template you have already submitted and had approved. You author and review the template once, and every send is a tiny payload.
POST /api/v1/rcs/messages/send
Authorization: Bearer <token>
Content-Type: application/json
{
"to": "+919876543210",
"template_id": "1f7a2c4e-9b0d-4e1a-8c3f-5d6e7f8a9b0c",
"variables": ["Rahul", "A-1042"]
}Body fields
How variables are filled
A template with this message text:
Hi {{1}}, your order {{2}} has shipped.can be sent with variables in either form:
and the recipient gets Hi Rahul, your order A-1042 has shipped.
Variables are filled in the message text, card titles and descriptions, button
labels, and at the end of a button link. A value in a link is encoded for a
web address (A 1/2 becomes A%201%2F2 in the path, a query value after a
?). Numbers and booleans are sent as text.
Rules
-
Every variable the template uses needs a non-empty value. If one is missing, nothing is sent or charged:
json{ "success": false, "code": "missing_variables", "missing": ["{{2}}"], "message": "This template needs a value for {{2}}. Send them in 'variables'." } -
The limits apply after filling: message text 2,000 characters, card title 200, card description 2,000, button label 25. A value that pushes a field over returns
400 variable_too_long. -
A
variablesvalue that is neither a list nor an object, or an object key that is not a number from1to10, returns400 invalid_variables. -
The template must be approved. A pending or rejected template returns
403withcode = "template_not_approved".
Shape 2, Composed message
For content that varies structurally per send. Put the content in message,
in the same shape as a template's components.
POST /api/v1/rcs/messages/send
Authorization: Bearer <token>
Content-Type: application/json
{
"to": "+919876543210",
"message": { "type": "text", "text": "Your OTP is 483920. Valid for 5 minutes." }
}Body fields
The content is checked against the same limits as a template. A violation
returns 400 with an errors[] array listing every problem.
Response (both shapes)
{
"success": true,
"message_id": "d8a7…",
"bot_id": "9c1b7f4e-2a11-4c8e-8e0e-3f5d1b2c9e01",
"cost": 0.3,
"sent_at": "2026-09-19T10:12:03Z"
}If you send faster than the bot's speed, the extra messages are accepted and
queued instead of refused. That response is 202 with "status": "queued",
and it goes out within seconds. Only when the queue is full do you get 429
with a Retry-After header.
Errors
See the Errors reference for the full list.
Examples, curl
Stored template with variables:
curl -X POST https://api.splashifypro.com/api/v1/rcs/messages/send \
-H "Authorization: Bearer $SPLASHIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"template_id": "1f7a2c4e-9b0d-4e1a-8c3f-5d6e7f8a9b0c",
"variables": {"1": "Rahul", "2": "A-1042"}
}'Composed text:
curl -X POST https://api.splashifypro.com/api/v1/rcs/messages/send \
-H "Authorization: Bearer $SPLASHIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"message": { "type": "text", "text": "Your OTP is 483920. Valid for 5 minutes." }
}'Examples, Node.js
// Send an approved template to one recipient with variable values.
const res = await fetch("https://api.splashifypro.com/api/v1/rcs/messages/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPLASHIFY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: order.customer_phone, // "+919876543210"
template_id: "1f7a2c4e-9b0d-4e1a-8c3f-5d6e7f8a9b0c",
variables: [order.customer_name, order.id],
}),
});
const { success, message_id, message } = await res.json();
if (!success) throw new Error(message);
console.log("Sent, message id:", message_id);Examples, Python
import os, requests
resp = requests.post(
"https://api.splashifypro.com/api/v1/rcs/messages/send",
headers={"Authorization": f"Bearer {os.environ['SPLASHIFY_API_KEY']}"},
json={
"to": order["customer_phone"],
"template_id": "1f7a2c4e-9b0d-4e1a-8c3f-5d6e7f8a9b0c",
"variables": [order["customer_name"], str(order["id"])],
},
timeout=30,
)
data = resp.json()
assert data["success"], data["message"]