> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# RCS onboarding: brand to launch

> Walk the full RCS Business Messaging lifecycle — register and submit your brand, create the agent, pass carrier verification, launch per carrier, and size your reach before the first campaign.

# RCS Onboarding: Brand to Launch

RCS sends go out under a **verified business agent**, and carriers only launch an agent after they have reviewed the business behind it. That makes RCS the longest onboarding path on the platform — brand capture, agent (bot) registration, carrier verification, and per-carrier launch are four separate gates, ordered, and none of them can be skipped. Only the first of them involves no waiting: you create the agent the moment the brand form is saved, because the brand and the agent travel to the carrier gateway in one combined submission.

This guide walks all four gates end to end, then the safe ramp: tester devices, capability checks, and reach sizing before your first campaign send. It complements the [RCS channel page](/channels/rcs), which covers message shapes (rich cards, carousels, suggested actions) — this guide is the ordered path to the first launched send.

## What RCS requires before any send

Four gates, in order. Each has a polling signal you can automate on:

| # | Gate             | You do                                        | Ends when                                                                       |
| - | ---------------- | --------------------------------------------- | ------------------------------------------------------------------------------- |
| 1 | **Brand**        | Register the business identity and submit it  | The brand is submitted — you can create the agent immediately, no approval wait |
| 2 | **Agent**        | Create the bot against the submitted brand    | Bot record exists (draft)                                                       |
| 3 | **Verification** | Submit screenshots + KYC docs to the carriers | `verification_status` is `verified`                                             |
| 4 | **Launch**       | Ask carriers to put the bot live              | `carrier_statuses` shows `launched` on ≥1 carrier                               |

Carriers decide gates 3 and 4 asynchronously, per carrier, so plan for **days, not minutes** — a rejected field early in the chain restarts the clock. Fill in every KYC field the first time.

## 1. Register the brand end to end

The brand carries the identity carriers review: name, website, logo, industry vertical, a contact person, and the postal address. Nothing is sent for review until you submit, so create the draft first and edit it until it is complete.

### Step 1: Create the draft brand

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/brands \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "brand_name": "Acme Retail",
    "brand_website": "https://acmeretail.com",
    "brand_logo_url": "https://acmeretail.com/assets/logo.png",
    "industry_vertical": "Retail",
    "contact_first_name": "Ada",
    "contact_last_name": "Wong",
    "contact_email": "ada.wong@acmeretail.com",
    "contact_phone": "+14155552671",
    "address_line_1": "100 Market Street",
    "city": "San Francisco",
    "state_province": "CA",
    "country": "US",
    "postal_code": "94103",
    "tax_id": "94-1234567",
    "legal_entity_name": "Acme Retail Inc."
  }'
```

The response is `201` with the stored brand in `draft` status. Keep `data.id` — every later call (update, submit, agent create) references it. A tax ID and legal entity name are optional on the API, but carriers in some markets (for example India and Brazil) ask for them before approving an agent, so send them when you have them.

**Handle the 422.** A `422` response names exactly which fields failed validation, so surface them in your form instead of treating the whole create as failed:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "brand_logo_url must use https; country must be a two-letter code",
    "status": 422
  }
}
```

### Step 2: Patch the draft until it is complete

Every field is optional on update — send only what changes. Edits are accepted only while the brand is `draft` (or `rejected` after a review); anything else returns `409`:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/rcs/brands/br_9x2k4m7 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "brand_logo_url": "https://cdn.acmeretail.com/logo-512.png" }'
```

### Step 3: Submit for review

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/brands/br_9x2k4m7/submit \
  -H "X-API-Key: dv_live_sk_..."
```

Submission moves the brand to `pending_review`. Re-submitting a rejected brand is safe: fix the fields named in `rejection_reason` and submit again — you do not start a new draft.

### Step 4: Check the status — but do not wait on it

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/rcs/brands/br_9x2k4m7 \
  -H "X-API-Key: dv_live_sk_..."
```

The status read is for visibility, not a gate. A brand in **any** status except `rejected` and `suspended` can back an agent — including a brand that was just submitted. The carrier gateway's own review of the combined brand + agent registration is the real approval step; no separate wait sits between your submission and agent creation.

| `status`         | What it means                                                                           |
| ---------------- | --------------------------------------------------------------------------------------- |
| `draft`          | Saved, not submitted                                                                    |
| `pending_review` | Submitted — can already back an agent; only an explicit refusal (`rejected`) blocks one |
| `approved`       | Recorded as screened — behaves the same as `pending_review` for agent creation          |
| `rejected`       | Refused for cause — fix the fields named in `rejection_reason` and re-submit            |
| `suspended`      | Taken down — contact support to reinstate                                               |

A lightweight alternative for readiness checks: `GET /api/v1/rcs/brands/summary` returns only `{ total, approved, pending }` counts, so a prerequisite banner does not have to page the full brand list.

## 2. Create the agent on the brand

Create the agent as soon as the brand is submitted — you do not wait for any approval. The brand details and the agent travel to the carrier gateway together in one combined registration, and the gateway's review verdict applies to both. Only a brand that was explicitly refused (`rejected`) or taken down (`suspended`) blocks agent creation:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/bots \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "br_9x2k4m7",
    "display_name": "Acme Retail",
    "description": "Order updates and support",
    "tos_url": "https://acmeretail.com/terms",
    "privacy_policy_url": "https://acmeretail.com/privacy",
    "contact_email": "support@acmeretail.com",
    "contact_phone": "+14155552671"
  }'
```

The contract carriers enforce:

* `brand_id` must point at a brand that is not refused — a brand in any status except `rejected` or `suspended` works; either of those returns `409`. There is no approval prerequisite.
* `description` is **100 characters or fewer**.
* `tos_url` and `privacy_policy_url` must be HTTPS links.
* At least one of `contact_phone` or `contact_email` — send both if you have them.
* Optional `bot_type` (`OTP`, `Transactional`, `Promotional`, `Multi-Use`), `region`, `platform`, `billing_category`, and `carrier_mccmnc` shape routing.

The response is `201`; the identifier the other bot endpoints take in their path is the bot's **`dotgoBotId`** from `GET /api/v1/rcs/bots`, not the `localId` from the create response. Fetch the list once after create to resolve it.

> **POST, not PUT.** Bot creation is `POST /api/v1/rcs/bots`. The `PUT /api/v1/rcs/bots` sibling is a full *update* of an existing registration — it replaces the stored creation payload rather than merging fields, and returns `404` when the brand has no bot yet. Never discover this by trial: use POST to create, PUT to correct a display name, description, legal URLs or contact details later.

## 3. Submit for verification

Verification is the carrier review every RCS agent passes before carriers let it message their subscribers. It expects files, so the request is `multipart/form-data`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/bots/bot_4kqzx1/verify \
  -H "X-API-Key: dv_live_sk_..." \
  -F 'data={"display_name":"Acme Retail"}' \
  -F "screenImages[]=@screens/welcome-card.png" \
  -F "screenImages[]=@screens/order-flow.png" \
  -F "kycdocs[]=@kyc/business-registration.pdf" \
  -F "brandLogoImage=@assets/logo.png"
```

File limits carriers enforce:

* `screenImages[]` — up to **10** conversation screenshots showing the bot's actual flows.
* `kycdocs[]` — up to **5** KYC documents (business registration, proof of address).
* `brandLogoImage` — optional, one logo file.
* Each file up to **5MB**.

The optional JSON `data` field overrides values otherwise filled in from the linked brand. On success the bot moves to `pending_verification`; a `409` means the bot and its brand do not carry enough detail to build a complete submission — finish the brand contact and address block first.

**Poll for the verdict:**

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/rcs/bots/bot_4kqzx1/quality \
  -H "X-API-Key: dv_live_sk_..."
```

Watch `verification_status` move to `verified`. The same read returns the bot's `status`, the per-carrier `carrier_statuses` map, and a launch summary — you will poll it again after launch.

## 4. Launch per carrier

Once the bot is `verified`, ask the carriers to put it live:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/bots/bot_4kqzx1/launch \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "carrier_mccmnc": ["310260", "310410"],
    "comment": "Production launch for order notifications"
  }'
```

* Launching before `verified` returns `409` `INVALID_LAUNCH_STATE`.
* `carrier_mccmnc` is an optional allow-list of MCCMNC ids to stage the rollout across a subset of networks — omit it to request all carriers.
* `comment` (up to 2000 characters) is shown to carriers with the request.

The bot moves to `pending_launch` immediately; each carrier's decision arrives later and lands in the bot's `carrier_statuses` map. Keep polling the quality endpoint — the bot is sendable once `launched` is true on **at least one** carrier (`carriers_launched` ≥ 1 of `carriers_total`). A bot launched on only some carriers cannot reach subscribers on the rest, so compare the map against where your audience sits before you scale traffic.

When a carrier has not decided yet, send on the carriers that have launched and let the org-level [RCS → SMS fallback](/channels/rcs#rcs-availability) cover the rest — or run the launch through the [production go-live checklist](/guides/go-live-checklist) with your team so number readiness, webhooks, and compliance gates are signed off before the first campaign.

## 5. Ramp safely before and after launch

### Preview on real devices before launch

An unlaunched bot can only message registered tester handsets, so add your own device first when building a rich-card flow. Register the handset directly:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/bots/bot_4kqzx1/devices \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+14155550123" }'
```

Or send the handset an on-device tester invite it can accept (`POST /api/v1/rcs/bots/:id/tester-invite` with the same `{ "phone" }` body) — a failed invite returns a retryable `503`, so it is safe to retry. Either way, the bot can then exchange messages with that number while still unlaunched, which is how you verify card rendering on real hardware.

### Check capability before a campaign send

Before spending sends on a recipient, probe the handset against your bot:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/rcs/capability/bot_4kqzx1/+14155550123 \
  -H "X-API-Key: dv_live_sk_..."
```

The response carries `isCapable`, the negotiated Universal Profile version, feature flags, and `fallback: "sms"` when the handset is not RCS-capable — so you can route the message down the SMS path in the same round trip.

### Size RCS vs SMS fallback per segment

Before building a campaign, estimate how much of a contact segment can actually receive RCS:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/rcs/reach-scan \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "bot_id": "bot_4kqzx1",
    "segment_id": "seg_lapsed_customers",
    "sample_size": 100
  }'
```

The scan samples up to `sample_size` contacts (default 50), probes each number's capability, and returns the capable share plus a recommended channel mix (`rcs`, `mixed`, or `sms`). Use it to decide between an RCS-only send and an SMS fallback split before you commit a segment to a rich-media campaign.

### Measure after launch

`GET /api/v1/rcs/analytics/templates?days=30` compares per-template sent / delivered / read / clicked rates so you can find which card earns taps before scaling a campaign onto it; `GET /api/v1/rcs/analytics/daily?days=30` breaks volume down per day to catch a delivery dip after a bot or template change.

## The no-wait path: test account with a real handset

Brand review takes days. If you want to send a real RCS message **today** — before provisioning your own brand — use the built-in test account, which sends through Devotel's shared, already-launched bot:

1. **Start verification** — `POST /api/v1/rcs/test-account/send-otp` with `{ "phone": "+14155550123" }`. The handset's RCS capability is checked and a tester invite is delivered to the device; the response carries a `verification_id`.
2. **Accept on the handset, then poll** — `POST /api/v1/rcs/test-account/verify-otp` with `{ "verification_id": "rcsver_..." }`. Verification completes when the tester invite is *accepted on the device*, not when you submit a code: while the invite is still pending the endpoint answers `425`, so keep polling every few seconds until it returns `200 { verified: true }`.
3. **Send demo traffic** — the verified handset receives demo sends from the shared bot within a per-day quota, enough to validate rendering and delivery.

The test account is for evaluation, not production: sends go out under the shared bot's branding, not yours, and the daily quota is deliberately small. To point a different device at it later, `POST /api/v1/rcs/test-account/change-number` clears the verified number (owner / admin only; the send quota carries over). Move to the brand flow above when you are ready for production identity.

## Troubleshooting

| Symptom                                | Cause and fix                                                                                                                                                                                     |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `409` on `POST /rcs/bots`              | The brand was explicitly refused (`rejected`) or taken down (`suspended`) — fix the fields named in `rejection_reason`, re-submit the brand, then create the agent. Any other brand status works. |
| `409` on `POST /rcs/bots/:id/verify`   | The bot's linked brand is missing contact or address detail — patch the brand, then resubmit.                                                                                                     |
| `409` `INVALID_LAUNCH_STATE` on launch | The bot is not `verified` yet — wait on the verification verdict first.                                                                                                                           |
| `425` on `test-account/verify-otp`     | The tester invite is still waiting on the handset — accept it on the device and keep polling.                                                                                                     |
| Sends skip a recipient after launch    | The recipient's carriers are not in the bot's `carrier_statuses` launched set, or the handset is not RCS-capable — check `GET /rcs/bots/:id/quality` and the capability endpoint.                 |
