Errors reference
Every RCS gateway rejection, whether at send time (sync response) or asynchronously via a delivery webhook, is translated to a customer-facing shape:
{
"success": false,
"message": "Recipient has opted out of RCS",
"detail": "Remove them from the list, don't retry.",
"category": "recipient",
"retryable": false
}message, one-line "what went wrong" title.detail, actionable "how to fix" line.category, colour-code bucket:content,media,recipient,config,quota,gateway,unknown.retryable, whether an automatic retry has any chance. Whentrue, the API will re-queue the send automatically.message_id, only when a send could not be confirmed: the network did not answer in time, or the connection broke after the message may have left. It can still arrive. If it does, its delivery webhook carries thismessage_idand the message is charged then; if it never arrives, nothing is charged.retryableisfalsehere, because sending it again could reach the recipient twice.
The same friendly title lands in failure_reason on the broadcast
targets endpoint so a broadcast's Targets view shows the resolution
hint directly.
Error categories
Common errors and how to fix them
Content
Media
Recipient
Config
Quota
Gateway (auto-retried)
Structural validator errors (at template create)
The /rcs/templates endpoint also enforces every rule above at
submit time (see Templates). A validator rejection
returns:
{
"success": false,
"message": "Template violates RCS limits. Fix the issues below and resubmit",
"errors": ["…", "…"],
"warnings": ["…"]
}Hard errors block the submit; warnings are informational (recommendations that hurt rendering but don't block sending).
API-level error codes
Non-gateway errors from our endpoints return a stable code field
alongside the message, so your client can programmatically react
to specific conditions:
{
"success": false,
"code": "no_active_bot",
"message": "Please activate an RCS bot before creating templates. Go to Apply for a bot."
}Multi-bot 409 conflicts
POST /rcs/apply runs a uniqueness gate on company_gstin and
brand_website. A conflict with a different account returns
HTTP 409 with a field key so your client can render the error
under the exact input:
Reusing your own GSTIN or brand website across multi-brand submissions on the same account is allowed and never triggers this conflict.
Broadcast / send with wrong bot
If bot_id isn't owned by the calling account or isn't in active
status, the endpoint returns HTTP 403:
{
"success": false,
"code": "bot_not_active",
"message": "This bot is not active. Wait for admin activation or pick another bot."
}When the status is specifically suspended, the sibling GET /rcs/bots/:bot_id
response includes a suspend_reason field so the panel can render it
inline, see Bots reference.
Broadcast on a non-promotional bot
A bot registered Transactional or OTP is provisioned for one-per-event traffic. Every bulk endpoint refuses it with HTTP 403:
{
"success": false,
"code": "bot_not_promotional",
"message": "\"Acme Orders\" is registered with the carrier as a Transactional bot, so it cannot run broadcasts. …",
"bot_use_case": "transactional",
"use_instead": "POST /api/v1/rcs/messages/send"
}This is not recoverable by retrying, topping up, or waiting, the bot's carrier registration has to change, which only support can do. The fix in your code is to route this bot's traffic through send message, which it can use without restriction.
Filter with the can_broadcast flag on GET /rcs/bots
to avoid the error entirely. A bot whose registration was never recorded
returns can_broadcast: true and is not refused.
Team member gates
Migration 283 introduced team members with per-bot scoping. These codes surface when a non-owner or scope-restricted caller hits an endpoint they're not authorised for. See the Team members reference for the role matrix.