> ## 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.

# Troubleshooting: channel and conversation pre-send refusals (CHANNEL_REQUIRED, CHANNEL_COMING_SOON, CHANNEL_NOT_REPLYABLE, CONVERSATION_NOT_FOUND, CONVERSATION_MISMATCH)

> Route a send or conversation reply rejected before dispatch to the channel- or conversation-name fail that fired — CHANNEL_REQUIRED, CHANNEL_COMING_SOON, CHANNEL_NOT_REPLYABLE, CONVERSATION_NOT_FOUND, CONVERSATION_MISMATCH — then fix the one field each envelope names.

# Troubleshooting: channel and conversation pre-send refusals

A send on any messaging channel runs a short set of *deterministic* checks
before it is ever handed to a provider. When one of those checks refuses the
request, the structured 404/422 envelope says exactly why — and the codes that
fire carry either a `channel` value or a `conversation_id` back, so you can
tell which field to fix. This page is the routing card for the five hardest
channel- and conversation-naming refusals a `POST /api/v1/messages` (and the
`POST /api/v1/notify` multi-channel fan-out, which dispatches through the
same send service) can throw. Definitions live in the
[Error Code Reference](/reference/error-codes); this page only owns the triage
path.

All five refusals are **deterministic** — the same body fails the same way
until you fix the named field. Never loop the retry.

## Section routing — which refusal fired

| Code | HTTP | The refusal names | First move |
| - | - | - | - |
| `CHANNEL_REQUIRED` | 422 | No/empty/`auto` channel | Pass a valid `channel` enum explicitly |
| `CHANNEL_COMING_SOON` | 422 | A regional/beta channel | Ask support to gate it in for your tenant |
| `CHANNEL_NOT_REPLYABLE` | 422 | An `agent`/`video` label | Pick a real outbound channel from the reply composer |
| `CONVERSATION_NOT_FOUND` | 404 | A `conversation_id` that resolved to nothing | Drop the field, or query the right id |
| `CONVERSATION_MISMATCH` | 422 | A `conversation_id` belonging to a different recipient/channel | Give the correct `conversation_id`, or omit it to auto-resolve |

## `CHANNEL_REQUIRED` — the send did not name its channel

**Cause.** The request body had no `channel`, an empty string, or the literal
`"auto"`. After 2026-09-01 every send must name its channel explicitly — the
unified send path used to guess one on `null`/`"auto"`, and measured behaviour
was that it picked VOICE for a text message on most input combinations
(paying PSTN rates and ringing the recipient's phone on an SMS send). Rather
than guess wrong again, the gate refuses the request outright.

**Fix.** Pass a valid channel name in the body — one of the enums on
[channels](/channels), e.g. `sms`, `whatsapp`, `rcs`, `email`, `voice`. If you
genuinely want the platform to pick a channel for you, do **not** send
`channel: "auto"` here — call
[`POST /messages/smart-send`](/api-reference/endpoints/messaging)
instead, which returns the chosen channel and its reasoning.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "CHANNEL_REQUIRED",
    "message": "A channel is required. Name one explicitly (e.g. \"sms\", \"whatsapp\", \"rcs\"), or call POST /messages/smart-send to have Orbit choose one and tell you which it picked.",
    "status": 422,
    "details": { "field": "channel" }
  },
  "meta": { "request_id": "req_7Xv4k2p9…", "timestamp": "2026-09-29T12:24:15.2Z" }
}
```

**Never retry.** The gate is deterministic — the same body without a channel
always fails.

## `CHANNEL_COMING_SOON` — a gated channel on a tenant that has not been enabled

**Cause.** The channel enum was valid, but it points at a regional or beta
channel that has not been enabled for your organization (e.g. an APAC
preview surface, or a channel your plan does not yet cover). The check is
tenant-scoped — the same channel may send fine for another tenant.

**Fix.** This is a tenant-side wait-for-enablement gate. Open a support ticket
asking for the channel to be gated in, listing (1) the exact channel name, (2)
your organization id (Settings → Organization, or `GET /api/v1/me`), and (3)
the intended destination region. Do **not** fall back to a sibling channel
unless the recipient can be reached there too — the refusal exists because a
beta channel's deliverability is not endorsed for general tenant traffic.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "CHANNEL_COMING_SOON",
    "message": "The requested channel is not generally available yet.",
    "status": 422,
    "details": { "channel": "line" }
  },
  "meta": { "request_id": "req_9Kd2p4nF…", "timestamp": "2026-09-29T12:25:02.8Z" }
}
```

**Do not confuse with** `CHANNEL_NOT_CONFIGURED` (the channel is enabled, but
the provider credentials/route for it were never completed under
**Settings → Channels**) or `CHANNEL_UNAVAILABLE` (enabled, but the fallback
chain was exhausted — see
[channel unavailable and fallback exhausted](/troubleshooting/channel-unavailable-fallback-exhausted)).

## `CHANNEL_NOT_REPLYABLE` — reply via an inbox-only label

**Cause.** In the Inbox / conversation-reply flow (and the
continue-on-channel surface on `POST /conversations/:id/resume-channel`),
the chosen channel string was one of the inbox-only triage labels — `agent`
(assign to an agent) or `video` (video call) — which are filters over the
conversation list, not outbound providers.

**Fix.** Pick a real outbound channel — `sms`, `whatsapp`, `email`, `voice`,
or another reachable one — from the reply composer. An `agent` or `video`
label exists only to route the thread; it never places an outbound provider
dispatch.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "CHANNEL_NOT_REPLYABLE",
    "message": "Cannot reply via \"video\" — this is an inbox view, not an outbound channel. Pick a real channel (sms, whatsapp, email, …) from the reply composer.",
    "status": 422
  },
  "meta": { "request_id": "req_2Fs7p4Ld…", "timestamp": "2026-09-29T12:25:41.9Z" }
}
```

**Do not confuse with** `MISSING_IDENTITY` — the label hits
`CHANNEL_NOT_REPLYABLE` only when the channel is an inbox view; a real channel
missing the recipient's identity on the conversation row throws the 400
`MISSING_IDENTITY` instead. Both are deterministic — fix the named field.

## `CONVERSATION_NOT_FOUND` — `conversation_id` resolved to nothing

**Cause.** `POST /messages` (and the `POST /notify` cascade, which re-uses
the same send service) was asked to thread the send into an
existing conversation via an optional `conversation_id`, but the lookup found
no such conversation on the caller's tenant — either because the id is
well-formed but unknown, or because it belongs to a different tenant. This is
an **idempotency scope guard**: a reply can never be threaded into a foreign
tenant's conversation by accident.

**Fix.** Drop the field entirely to let the send auto-resolve the thread (the
recipient + channel will open a new conversation or reuse the latest open
one), or re-query to get the right conversation id via
`GET /conversations?contact=…` before threading.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "CONVERSATION_NOT_FOUND",
    "message": "conversation_id does not reference a conversation on your account.",
    "status": 404,
    "details": {
      "field": "conversation_id",
      "conversation_id": "conv_0192f2e2-89a1-4f2d-b222-2f0c69heA3"
    }
  },
  "meta": { "request_id": "req_4Gk9v2Qw…", "timestamp": "2026-09-29T12:26:11.4Z" }
}
```

**Never retry.** Same id, same 404 — until the thread row exists.

**Do not confuse with** the threads page diagnosing
`CONVERSATION_TERMINAL` — a conversation that *exists* but is closed,
archived, or snoozed, and so cannot accept replies on any channel.

## `CONVERSATION_MISMATCH` — `conversation_id` belongs to a different recipient/channel

**Cause.** The `conversation_id` *did* resolve — but either the conversation's
`contact_phone`/`contact_email` does not equal the send's `to` recipient, or
the conversation's primary `channel` (and its cross-channel `channels[]`
union) does not include the send channel. This is the **anti-misroute guard**:
it stops a reply being threaded into another contact's open thread (or an SMS
reply stapled onto a WhatsApp thread).

**Fix.** Give the correct `conversation_id` — the one whose recipient and
channel actually address this send — or omit the field so the send
auto-resolves the thread by recipient. Query with
`GET /conversations?contact=<to>` to pick the right row.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "CONVERSATION_MISMATCH",
    "message": "conversation_id does not belong to this recipient and channel. Thread a reply only into a conversation that already addresses the same recipient on the same channel, or omit conversation_id to auto-resolve the thread by recipient.",
    "status": 422,
    "details": {
      "field": "conversation_id",
      "conversation_id": "conv_0192f2e2-89a1-4f2d-b222-2f0c69heA3"
    }
  },
  "meta": { "request_id": "req_7Pp2k4Ns…", "timestamp": "2026-09-29T12:26:48.3Z" }
}
```

**Never retry.** In both cases the guard is deterministic — fix the
recipient or channel match before re-sending.

## Where each refusal routes

1. `CHANNEL_REQUIRED` — `POST /api/v1/messages` (its explicit-channel guard
   runs first). Call [`POST /messages/smart-send`](/api-reference/endpoints/messaging)
   to have the platform choose a channel and report it back.
2. `CHANNEL_COMING_SOON` — the custom-channel gate inside the message-send
   bootstrap; enable the tenant over support before the channel will route.
3. `CHANNEL_NOT_REPLYABLE` — the
   [`/conversations/:id/reply`](/api-reference/endpoints/messaging#conversations)
   flow and the `continue-on-channel` surface; pick a real outbound channel.
4. `CONVERSATION_NOT_FOUND` / `CONVERSATION_MISMATCH` — the anti-misroute
   thread guard on `POST /api/v1/messages` (and the `POST /notify` cascade
   that re-uses the send service). Field name is `conversation_id`.

## When to escalate

A `CHANNEL_COMING_SOON` on a channel you believe **is** released to your
plan is abnormal; so is `CONVERSATION_MISMATCH` on a thread id that you can
see in the Inbox itself. Escalate to support with:

1. **Your organization ID** — Settings → Organization, or `GET /api/v1/me`.
2. **The exact code and `meta.request_id`** from the rejected envelope.
3. **For `CHANNEL_COMING_SOON`**: the channel name and the destination region.
4. **For `CONVERSATION_NOT_FOUND` / `CONVERSATION_MISMATCH`**: the
   `conversation_id` you passed, the conversation id you see in the Inbox for
   that thread, and the recipient `to` + `channel` of the send.

## See also

* [Error Code Reference](/reference/error-codes) — definitions for every
  code routed here.
* [Messaging queues and receipts](/reference/troubleshooting#messaging-queues-and-receipts) —
  the non-error parking states (`queued`, `scheduled`, `pending`) that hold a
  send before any channel gate runs.
* [Send-price and country gates](/troubleshooting/send-price-and-country-gates) —
  the price-ceiling and country-allowlist gates that fire beside these.
* [Pricing-gate errors](/troubleshooting/pricing-gates) — the billing-family
  `PRICING_*` codes (rate card, FX source, override writes) on the pricing
  side of the send path.
* [Template variant missing](/troubleshooting/template-variant-missing) —
  the pre-send variant-resolution refusal `NO_VARIANT_FOR_CHANNEL`.
* [Pre-send gates on messaging](/troubleshooting/messaging-pre-send-gates) —
  the blocked-contact, quiet-hours, and session-window gates that share the
  same pre-send chain.
* [Smart send](/api-reference/endpoints/messaging) — the
  `POST /messages/smart-send` path to have Orbit choose a channel.
