Templates
Team-member access: operators can create + delete templates; viewers can list + get but get
403 not_operatoron any mutation. Every template is bot-scoped (seebot_idbelow) and every member sees every template on the account. See the Team members reference.
Templates are the message blueprints you'll later attach to a broadcast. Every template is created against a specific active bot and goes through two verification steps:
- Structural validator, runs synchronously at submit time.
Rejects the request when the payload violates RCS limits (title
length, media size, carousel count, button rules, …). Returns a
structured
errors[]array so your UI can highlight every issue at once. - Content review, the template is checked against RCS content
policy. The verdict lands on
ai_review_status, visible onGET /api/v1/rcs/templates/{id}. Allow up to 24 hours; most templates clear sooner, and anything needing a closer look is seen by our team.
Poll a single unified endpoint,
GET /api/v1/rcs/templates/{id}/status, for the authoritative
approval verdict.
You need an active RCS bot before you can create templates.
POST /templatesreturnsHTTP 400 code=no_active_botif you haven't activated one yet. Apply for a bot athttps://rcs.splashifypro.com/apply-rcs.
POST /api/v1/rcs/templates
GET /api/v1/rcs/templates
GET /api/v1/rcs/templates/{id}
GET /api/v1/rcs/templates/{id}/status
DELETE /api/v1/rcs/templates/{id}Create a template
POST /api/v1/rcs/templates
Authorization: Bearer <jwt>
Content-Type: application/json
{
"bot_id": "2a4d9a66-7e87-4057-bee7-662879612c21",
"name": "order_shipped",
"category": "transactional",
"language": "en_US",
"components": {
"type": "text",
"text": "Your order #{{1}} has shipped. Track at {{2}}."
},
"suggestions": []
}Variables, {{n}} placeholders
Variables are {{1}}, {{2}}, … up to {{10}}, numbered from {{1}}
with no gaps. They may appear in:
- the message
text - a card
titleanddescription - a suggestion's label (
text) - the end of a suggestion
url, e.g.https://shop.com/track/{{2}}. The value is URL-encoded:A 1/2becomesA%201%2F2in a path, and after a?it is encoded as a query value.
They may not appear in a card's media url, its thumbnail, or a
dial suggestion's call_to.
The values come from a broadcast's column mapping (see
Broadcasts)
or from variables on a single send.
Attach a variables array on Create with a label and a sample for
each variable:
{
"variables": [
{ "index": 1, "label": "customer_name", "sample": "Rahul" },
{ "index": 2, "label": "order_id", "sample": "A-1042" }
]
}The label is what the broadcast mapper shows next to each variable. The sample is optional through the API but recommended: with samples, the template is checked with them filled in, so a value that would push the card title past 200 characters is caught now. Without them only the structure is checked. Labels and samples never change what gets sent.
The limits below apply to the text after filling: text 2000
characters, card title 200, card description 2000, suggestion label 25.
A template that breaks a variable rule is refused with HTTP 400:
{
"success": false,
"code": "invalid_variables",
"field": "variables",
"message": "Number the variables from {{1}} with no gaps: {{2}} is missing"
}Other messages you can get:
A template can have at most 10 variables, {{1}} to {{10}}A variable in the button 1 link must be at the end of the link(names the field)A variable can't go in the card image or video. Use variables in the text, titles, button labels, or at the end of a linkWith the sample values filled in: The card title is over 200 characters after filling the variables
Components shape
Three top-level type values are supported:
1. Basic (plain text with optional quick-reply chips)
{
"type": "text",
"text": "Your order #{{1}} has shipped. Track at {{2}}.",
"suggestions": [
{ "type": "message", "text": "Track order", "postback": "track_click" }
]
}2. Rich card (single card with media + buttons)
{
"type": "card",
"card": {
"title": "Diwali sale, 40% off",
"description": "Every top brand, ends midnight.",
"url": "https://cdn.example.com/hero.jpg",
"suggestions": [
{ "type": "url", "text": "Shop now", "url": "https://shop.example.com", "postback": "shop_click" },
{ "type": "dial", "text": "Call us", "call_to": "+919876543210", "postback": "dial_click" }
]
}
}3. Carousel (2–10 rich cards)
{
"type": "multiple_cards",
"cards": [
{ "card": { "title": "Card 1", "description": "…", "url": "https://…", "suggestions": [ … ] } },
{ "card": { "title": "Card 2", "description": "…", "url": "https://…", "suggestions": [ … ] } }
]
}A card can set media_height to SHORT, MEDIUM (the default) or
TALL. A card or carousel message can also carry its own
suggestions next to card or cards. They show below the cards and
follow the same button rules, up to 11.
Button (suggestion) types
Give a location button latitude and longitude where you can. A
query alone works only on bots that can show a map search. On other
bots that button is left out and the rest of the message still goes,
and the validator returns a warning for it.
Limits, the validator enforces all of these
Any violation returns HTTP 400 with an errors[] array. Warnings
(recommendations that hurt rendering but don't block sending) come
back in warnings[]. They don't stop the submit.
Character limits
Structural limits
Media limits
The panel's upload widget rejects oversize files client-side; the API re-enforces on every submit.
Media URLs must be https:// and publicly reachable, the gateway
downloads them at send time; a 404 or private URL fails the send with
"Media not accessible".
TTL / expiry
ttlandexpireTimeare mutually exclusive.ttlmust end in"s"(e.g."50s").- Both cap at 15 days.
Response, success
{
"success": true,
"template_id": "1f7a…",
"status": "pending",
"message": "Template submitted for review. Most templates are approved within 24 hours."
}Poll GET /api/v1/rcs/templates/{id}/status for the verdict. Most templates
are approved within 24 hours.
Response, no active bot (HTTP 400)
{
"success": false,
"code": "no_active_bot",
"message": "Please activate an RCS bot before creating templates. Go to Apply for a bot.",
"hint_url": "/apply-rcs"
}Emitted when the caller has no active bot on the account. Direct
your users to the apply-rcs flow. A companion bot_not_active code
is returned when the target bot exists but hasn't been activated by
an admin yet.
Response, validator rejection (HTTP 400)
{
"success": false,
"message": "Template violates RCS limits. Fix the issues below and resubmit",
"errors": [
"Carousel card 3: title is 217 characters, max 200",
"Carousel card 3 → button 1: label is 34 characters, max 25",
"Carousel card 5: media URL must start with https://"
],
"warnings": [
"Rich card: 3 buttons. We recommend ≤ 2 per card to reduce cropping"
]
}Get one template
GET /api/v1/rcs/templates/{id}{
"success": true,
"template": {
"template_id": "1f7a…",
"bot_id": "2a4d9a66-7e87-4057-bee7-662879612c21",
"provider": "jiocx",
"name": "order_shipped",
"category": "transactional",
"language": "en_US",
"components": { "type": "text", "text": "Hi {{1}}, your order {{2}} shipped." },
"suggestions": [ ],
"variables": [
{ "index": 1, "label": "customer_name", "sample": "Rahul" },
{ "index": 2, "label": "order_id", "sample": "" }
],
"ai_review_status": "approved",
"ai_review_reason": "Clean transactional template.",
"effective_status": "approved",
"submitted_at": "2026-05-27T08:31:21Z",
"reviewed_at": "2026-05-27T08:31:24Z"
}
}effective_status is what broadcasts check. ai_review_status
carries the content-review verdict, and provider is the gateway
resolved from the template's bot.
variables lists every variable the content uses, in order, even one
created without a label: label and sample are empty strings when they were
not set. A template with no variables returns an empty list.
List templates
GET /api/v1/rcs/templatesReturns { "success": true, "templates": [ … ] } with the same row
shape as GET /…/{id}, ordered by submission time desc.
Check template approval status
One unified endpoint returns the authoritative approval verdict for every template.
GET /api/v1/rcs/templates/{id}/status
Authorization: Bearer <jwt>Response:
{
"success": true,
"status": "approved",
"ai_status": "approved",
"reason": "Clean transactional template.",
"score": 0.94,
"checked_at": "2026-07-23T14:03:22Z"
}status, the human-readable verdict (pending,approved,rejected, …). This mirrorseffective_statuson the standard Get and is what broadcasts check.ai_status, the content-review verdict for the template.reason/score, the justification and a confidence score.checked_at, when the verdict was last computed.
Error cases:
Delete
DELETE /api/v1/rcs/templates/{id}Removes the template. Broadcasts already running with this template keep their assignment and won't break.
Email notifications
Every template state transition emails the account owner:
- Template submitted → "Template in review"
- Approved → "Template approved"
- Rejected → "Template needs changes" (with reason)
Test send
Preview a template on your own phone before submitting for review. The send never touches your wallet, never counts against your broadcasts, and never triggers billing.
Endpoint
POST /api/v1/rcs/test-send
{
"bot_id": "<uuid>",
"template_id": "<uuid>" // OR provide "message": <raw message content>
}Either template_id (from an existing saved template, approved or
not) or an inline message (the raw message content JSON) is required. The
server renders template_id the same way a broadcast send does.
Recipient
Always me.test_phone_number || me.phone. There is no to field.
The composer button reads back the phone in the label so the operator
can confirm where it lands (Send test to +91987…321).
Set the override on PATCH /api/v1/rcs/settings/test-phone:
PATCH /api/v1/rcs/settings/test-phone
{ "test_phone_number": "+919876543210" }Sending { "test_phone_number": "" } clears the override and the
account phone is used instead.
Response
{
"success": true,
"test_send_id": "018e5f01-…",
"msg_id": "018e5f01-…",
"phone": "+919876543210"
}History
GET /api/v1/rcs/test-sends?limit=20{
"success": true,
"test_sends": [
{
"test_send_id": "018e5f01-…",
"bot_id": "…",
"template_id": "…",
"phone": "+919876543210",
"msg_id": "…",
"status": "delivered",
"sent_at": "2026-07-14T09:12:03Z",
"delivered_at": "2026-07-14T09:12:04Z",
"read_at": "0001-01-01T00:00:00Z",
"failure_reason": ""
}
]
}Real-time status
Status transitions arrive on the panel event stream. Consumers should
key off test_send_id (matches the POST /test-send response).
{
"type": "test_send.status",
"data": {
"test_send_id": "018e5f01-…",
"status": "delivered",
"failure_reason": ""
}
}Status values: queued, sent, delivered, read, failed.
Rules
- Bot must be in the caller's scope AND
activeAND provisioned (agent id + agent key). See the errors reference forbot_not_active/bot_not_provisioned. - Test sends bypass the account's opt-out list, the target is the operator themselves.
- Never billed.
applyPanelSuccessBillingskips any msg_id that resolves to arcs_test_sends_by_msgrow. Prepaid wallets aren't debited; postpaid usage counters aren't bumped. - Viewers get
403 not_operator.