Bots
Every RCS Panel account can operate multiple bots. Each bot has its own KYC application, its own provider-side identity, and its own transactions-per-second (TPS) budget. Wallet balance is shared: every bot debits the same wallet.
The typical customer lifecycle:
- Apply for a new bot via
POST /rcs/apply. - Wait for admin review, the KYC application is approved.
- Admin activates the bot with provider credentials, TPS, and price.
- The bot's
statusflips toactiveand it can send messages.
Template verification and media
Every submitted template is reviewed against RCS content policy before it can
be used; the verdict lands on ai_review_status. Most are approved within 24
hours, though
most clear sooner. Rich cards and
carousels are fully supported, see the
templates reference for the full field list and
validator rules.
Media referenced by a template is stored on Splashify Pro's CDN so sends do not have to fetch from your origin.
GET /rcs/bots includes a provider field on every row, always
filled. Treat it as an opaque label. To decide
what a bot can do, read can_broadcast and can_check below, and
check_bot_ok from the
audience endpoint
for number checks, rather than branching on provider.
Uniqueness rules
Two fields are unique across accounts to prevent brand impersonation:
- GSTIN, one GSTIN cannot be used by two different accounts.
- Brand website, one brand website cannot be used by two different accounts.
The same account can reuse both fields across multiple bots (multi-brand company case). The uniqueness check normalises values before comparison:
- GSTIN, uppercase + trim.
- Brand website, lowercase, strip
http:///https://prefix, strip trailing/.
If either value is already registered under a different account, the
POST /rcs/apply call returns 409 Conflict with:
{
"success": false,
"field": "gstin",
"message": "This GSTIN is already registered under another RCS account. Contact support with proof of ownership if this is incorrect."
}List your bots
GET /api/v1/rcs/bots
Authorization: Bearer <rcs_panel_jwt>Response:
{
"success": true,
"bots": [
{
"bot_id": "e8bb61e0-8500-11ef-9c9a-4bc60d7a1e2f",
"bot_name": "Acme Support",
"provider": "jiocx",
"agent_id": "acme_support_agent_01",
"status": "active",
"is_default": true,
"tps": 100,
"rcs_price": 0.35,
"use_case": "transactional",
"use_case_label": "Transactional",
"can_broadcast": false,
"can_check": true,
"activated_at": "2026-07-10T12:00:00Z"
},
{
"bot_id": "2a4d9a66-7e87-4057-bee7-662879612c21",
"bot_name": "Acme Marketing",
"provider": "jiocx",
"agent_id": "acme_marketing_agent_01",
"status": "active",
"is_default": false,
"tps": 200,
"rcs_price": 0.45,
"use_case": "promotional",
"use_case_label": "Promotional",
"can_broadcast": true,
"can_check": true,
"activated_at": "2026-07-23T09:15:00Z"
}
]
}agent_id is the opaque provider-side identifier for the bot;
treat it as read-only in the panel API.
use_case and can_broadcast
use_case is the traffic type the bot is registered for with the carrier,
normalised to one of promotional, transactional, otp, multi_use, or
"" when it was never recorded. use_case_label is the same value formatted
for display.
can_broadcast is the derived answer to "may this bot run a broadcast?":
false for transactional and otp, true for everything else including an
unrecorded registration.
Read the flag, do not re-derive it
can_broadcast exists so the rule lives in exactly one place. Deriving it yourself from
use_case works right up until the rule changes, at which point your client and the API
disagree and your users see a 403 the UI told them would not happen.
Broadcast, recurring-broadcast and retarget endpoints return 403
bot_not_promotional when can_broadcast is false, see
Broadcasts.
One-off sends are unaffected.
can_check
Pass a bot with can_check: true to
a list's number check. A bot that is not live on the
network yet can have can_check: true too: its check then stops at once and says so.
Get one bot
GET /api/v1/rcs/bots/{bot_id}Apply for a new bot
Applying is how a bot comes into existence. There is no create-a-bot call: you submit a KYC application, the carrier reviews it, and the bot appears on the account when that clears.
It takes two steps, and they have to happen in that order. Files are uploaded first and return URLs; the application then references those URLs. The submit call is JSON and never carries a file.
Scope
POST /apply and POST /apply/upload need bots:write. Reading your own
application back with GET /apply needs bots:read.
Step 1: upload the documents
POST /api/v1/rcs/apply/upload
Content-Type: multipart/form-dataTwo form fields: field names which document this is, and file is the file
itself.
curl -X POST https://apis.splashifypro.com/api/v1/rcs/apply/upload \
-H "Authorization: Bearer rcs_pk_live_..." \
-F "field=bot_logo" \
-F "[email protected]"{ "success": true, "url": "https://folder.splashifypro.com/rcs-applications/.../logo.png" }The rules below are the carrier's, not ours, and are rejected at upload rather than at submission so a mistake surfaces immediately:
The two image sizes are exact, not maximums. A 225 x 224 logo is refused.
Step 2: submit the application
POST /api/v1/rcs/apply
Content-Type: application/jsonRequired on submission. A draft may omit any of them; a submission may not:
Everything else is optional: company_gstin, company_address, company_zip,
registered_company_name, company_hq_location, transaction_type,
business_pan_url, cin_url, corporate_registration_url,
electricity_bill_url, optin_video_url, brand_color, industry_type,
languages, the phone_label / website_label / email_label display
labels, and the contact_* fields.
curl -X POST https://apis.splashifypro.com/api/v1/rcs/apply \
-H "Authorization: Bearer rcs_pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"bot_name": "Acme Support",
"brand_name": "Acme",
"bot_message_type": "Promotional",
"short_description": "Order updates and offers from Acme",
"bot_logo_url": "https://folder.splashifypro.com/.../logo.png",
"banner_image_url": "https://folder.splashifypro.com/.../banner.png",
"business_manager_name": "Priya Sharma",
"brand_support_phone": "+919876543210",
"support_email": "[email protected]",
"brand_website": "https://acme.example",
"privacy_url": "https://acme.example/privacy",
"terms_url": "https://acme.example/terms",
"contact_us_url": "https://acme.example/contact"
}'{
"success": true,
"status": "submitted",
"bot_id": "e8bb61e0-8500-11ef-9c9a-4bc60d7a1e2f",
"updated_at": "2026-07-15T09:12:33Z"
}A missing required field returns 400 naming exactly which one:
{ "success": false, "message": "banner_image_url is required" }Drafts
Send "save_as_draft": true to store a partial application. A draft does not
allocate a bot_id and does not run the uniqueness gate, so you can iterate on
it freely and submit when it is complete.
Resubmitting a rejected application
Include the existing bot_id and the call updates that application instead of
starting a new one. Without it you get a second bot rather than a corrected
first one, which is the mistake this field exists to prevent.
A rejection comes in one of two kinds. GET /api/v1/rcs/apply?bot_id=… tells
them apart with resubmit_blocked, and the same flag is on every bot in
GET /api/v1/rcs/bots:
Reading it back
GET /api/v1/rcs/applyReturns the stored application and its current status, which is how you poll for the outcome of a review.
default_bot_id on /me
For clients still shipping the old single-bot request shape, the
/rcs/me response now includes default_bot_id, the bot marked
is_default=true, so you can seed dropdowns without an extra
/rcs/bots call:
{
"success": true,
"user": {
"user_id": "…",
"default_bot_id": "e8bb61e0-8500-11ef-9c9a-4bc60d7a1e2f",
"bots_count": 3,
"active_bots_count": 2,
"pending_bots_count": 1
}
}Sending a broadcast or one-off message without a bot_id field
defaults to this bot for one release cycle; new integrations should
pass the exact bot_id.
Change the default bot
POST /api/v1/rcs/bots/{bot_id}/default
Authorization: Bearer <rcs_panel_jwt>Makes an active bot the account's default. No request body. The bot that was the default loses the flag in the same step, so the account always has exactly one.
The default bot is:
- the bot a call uses when it has no
bot_id: a single send, broadcast create, template review and the audience count default_bot_idon/rcs/me- the bot the panel picks first in the broadcast, recurring broadcast and template composers, and on the number check and opt-out pages
Every change is recorded in the audit log as bot.default_changed.
{
"success": true,
"default_bot_id": "2a4d9a66-7e87-4057-bee7-662879612c21"
}Calling it for the bot that is already the default returns the same 200 and
changes nothing.
Who can call it
Only the account owner, from a signed-in panel session. Operators and viewers get 403
not_owner. An API key gets 403 api_key_endpoint_not_allowed at any scope, because this
call changes where every request without a bot_id goes.
{
"success": false,
"code": "bot_not_active",
"message": "Only an active bot can be the default."
}When a bot is suspended
The admin team can suspend a single bot independently of the account.
Only that bot is blocked, every other bot on the account keeps
sending as normal. Broadcasts or one-off sends that target a
suspended bot return HTTP 403 with the bot_not_active code
(same shape you get for pending / rejected bots; see
Errors reference).
The GET /rcs/bots/:bot_id and GET /rcs/bots responses expose two
extra fields when the bot is suspended:
{
"bot_id": "e8bb61e0-…",
"bot_name": "acme_promotions",
"status": "suspended",
"suspend_reason": "Repeated content-policy violations after two warnings, see support ticket #4210."
}suspend_reason is a verbatim quote of the admin's stated reason so
the panel can render it inline. The customer also gets a
"Your RCS bot <name> has been suspended" email carrying the
same reason.
Reactivation (admin flips the bot back to active) fires
"Your RCS bot <name> is active again"; the suspend_reason
field is cleared and sends unblock immediately.
Team-member access
Operators and viewers only see and act on bots that the account owner
granted them. When their JWT bots scope excludes a bot, any endpoint
that targets it returns 403 bot_not_assigned, see the
Team members reference for the full role matrix.