Send RCS message
Fire a one-off RCS message from your provisioned sender. For bulk sends use Broadcasts; this endpoint is for the programmatic side — order shipped, OTP, support reply.
POST /api/v1/rcs/messages/send
Authorization: Bearer <jwt>
Content-Type: application/json
{
"to": "+919876543210",
"message": { "type": "text", "text": "Hi {{1}}, your order #{{2}} has shipped." },
"extra": "order_shipped_acme_42"
}Prerequisites
- Your account must be in
status = activeandrcs_active = true(i.e. an admin has provisioned your RCS sender). If not, the endpoint returns 403 withcode: "rcs_not_active". Apply at/apply-rcsto start that. - Wallet balance must be
>= rcs_per_msg_price. The price is debited on a successful gateway accept; rejections refund automatically.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
to | string | yes | E.164 (e.g. +919876543210) |
message | object | yes | Free-form RCS payload — text, rich card, carousel, suggestions, media |
extra | string | no | Free-form passthrough metadata; surfaces on the delivery webhook so you can correlate sends |
The sender identity is derived from your JWT — you do not pass
bot_name or any sender override in the body.
Message shapes
Plain text
{
"type": "text",
"text": "Your order #12345 has shipped. Track at https://acme.com/o/12345"
}Text with suggestions
{
"type": "text",
"text": "Anything else?",
"suggestions": [
{ "type": "reply", "text": "Track order", "postback": "track-12345" },
{ "type": "url", "text": "Open site", "url": "https://acme.com" },
{ "type": "dial", "text": "Call us", "call_to": "+919876543210" }
]
}Rich card
{
"type": "card",
"title": "Order #12345 shipped",
"description": "Tap to track. ETA Wed.",
"media_url": "https://cdn.acme.com/banners/order-shipped.jpg",
"suggestions": [
{ "type": "url", "text": "Track", "url": "https://acme.com/o/12345" }
]
}Carousel
{
"type": "multiple_cards",
"cards": [
{ "title": "Black tee", "description": "₹499", "media_url": "https://…/tee.jpg", "suggestions": [{"type":"url","text":"Buy","url":"https://acme.com/p/tee"}] },
{ "title": "Blue jeans", "description": "₹1299","media_url": "https://…/jeans.jpg", "suggestions": [{"type":"url","text":"Buy","url":"https://acme.com/p/jeans"}] }
]
}Response
200 OK:
{
"success": true,
"message_id": "rm_AbCd12…",
"cost": 0.95,
"sent_at": "2026-05-28T08:00:02Z"
}message_id is what the gateway returned — keep it if you want to
correlate against delivery webhooks. cost is the wallet debit.
Error codes
| Status | When | Notes |
|---|---|---|
| 400 | Missing to or message | Body validation |
| 402 | Wallet balance below rcs_per_msg_price | Recharge and retry |
| 403 | rcs_not_active | Apply for RCS sender first |
| 403 | account suspended | Contact support |
| 502 | Gateway rejected the send | detail carries the upstream response |
| 503 | Gateway login or transport failure | Safe to retry; the wallet is refunded |
Idempotency
Every POST is treated as a new send. If you retry on a network error
you may get duplicate messages. Put your own idempotency key in
extra and dedupe on the delivery callbacks.