> ## 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: SAFETY_CLASSIFIER_REFUSAL, AGENT_CHANNEL_CAP_EXCEEDED, and EMPTY_AUDIENCE_NOT_ALLOWED

> Recovery ladders for the three deterministic 4xx refusals on AI and audience surfaces: the safety-classifier 403 on prompt assist and trait auto-derive, the per-agent per-channel concurrency-cap 422 on assignment, and the zero-member audience 422 on campaign create.

# Troubleshooting: AI guardrail refusals and audience blocks

Three refusals on the AI and audience surfaces share a useful property:
each one is **deterministic** and each envelope names exactly what to fix.
A 403 from the safety classifier means the request content was refused; a
422 from the channel cap names the channel, the current count, and the
ceiling; a 422 from campaign create names the audience that matched zero
contacts. None of the three is a transient platform fault, so none of them
yields to a blind retry. This page owns the recovery ladder for each code;
definitions live in the [Error Code Reference](/reference/error-codes).

## Section routing — which refusal fired

| Code | HTTP | Where it surfaces | First move |
| - | - | - | - |
| `SAFETY_CLASSIFIER_REFUSAL` | 403 | Prompt assist in the agent studio; CDP trait auto-derive on a contact; any AI completion behind the platform LLM gateway | Revise the input, then resubmit |
| `AGENT_CHANNEL_CAP_EXCEEDED` | 422 | Assigning a conversation or inbox ticket to an agent | Free a slot, raise the cap, or spread the load |
| `EMPTY_AUDIENCE_NOT_ALLOWED` | 422 | Creating a campaign whose audience is a segment or a list | Preview the matched count, fix the audience, then create |

## `SAFETY_CLASSIFIER_REFUSAL` — the safety classifier refused the completion

**Cause.** When an AI completion is requested, the platform LLM gateway
runs the response through server-side safety classifiers. When a
classifier refuses to produce the output, the gateway blocks the refused
content and returns this 403 instead of passing anything through. You see
it on two customer-facing surfaces today:

* **Prompt assist** in the agent studio (the improve-prompt action on a
  draft agent prompt): the draft you submitted is what got classified.
* **Trait auto-derive** on a contact profile in the CDP: the contact's
  recent event mix is summarised into the request, so the refusal is about
  that derived content, not about anything you typed.

Any other surface that requests an AI completion through the same gateway
returns the same code, so the ladder below applies everywhere.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "SAFETY_CLASSIFIER_REFUSAL",
    "message": "This request was refused by Anthropic's safety classifiers and was blocked before reaching you.",
    "status": 403,
    "details": {
      "category": "…",
      "explanation": "…"
    }
  },
  "meta": { "request_id": "req_8Tq2mW4x…", "timestamp": "2026-10-16T09:14:22.7Z" }
}
```

`details.category` names the classifier that fired and
`details.explanation` says what in the content tripped it. Start there.

**Retry vs revise.** The refusal is about the *content* of the request,
not about load, quota, or a transient fault. Resubmitting the identical
payload returns the identical 403, so never loop the retry. Revise first,
then resubmit: a revised payload is a new classification, not a retry.

**Recovery ladder.**

1. Read `details.category` and `details.explanation` from the envelope.
2. Inspect the payload you sent. For prompt assist, that is the draft
   prompt text: remove or reword the flagged content (quoted material,
   instructions that read like impersonation or deception, personal data
   pasted into the draft) while keeping the intent. For trait
   auto-derive, the input is derived from the contact's own event history,
   so there is nothing for you to edit; treat the refusal as final for
   that contact and rely on the contact's other traits.
3. Resubmit once revised. A clean payload completes normally.
4. If the flagged content looks like a false positive (ordinary support
   language, a product name, a quoted customer message), escalate to
   support with the `meta.request_id` and the action you attempted; do not
   keep rewording blindly.

**Do not confuse with** the tenant-configurable spend and rate caps
(`AGENT_DAILY_SPEND_CAP_REACHED` and siblings, 429s with a `retry_after`,
covered in [Squad and conversation cost caps](/troubleshooting/squad-and-conversation-cost-caps))
or with opaque upstream failures (502s, covered in
[LLM upstream failures](/troubleshooting/llm-upstream-failures)). A 429
asks you to wait; a 502 asks you to retry later; this 403 asks you to
change the content.

**Tenant-owned posture.** Two layers ride on your AI surfaces. The safety
classifier is the platform floor: it is fixed, applies to every tenant,
and is not tunable per tenant, so a `SAFETY_CLASSIFIER_REFUSAL` can never
be switched off from your side. On top of it, *your* guardrail posture is
tenant-owned: the agent settings panel in the studio exposes guardrail
toggles that set how strictly each of your agents handles personal data,
competitor mentions, and off-topic requests. Adjust those toggles to
change what your agents say; they do not raise or lower the platform
floor this 403 comes from.

## `AGENT_CHANNEL_CAP_EXCEEDED` — the agent is at the per-channel ceiling

**Cause.** An assign put (or would put) one more conversation of a channel
onto an agent who is already at the concurrency ceiling you configured for
that channel, or who is opted out of the channel entirely (a cap of 0).
The gate runs on manual assignment, self-claim from the inbox, bulk
assign, and ticket reassignment; automated routing (routing rules,
round-robin auto-assign) skips at-cap agents instead of failing.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "AGENT_CHANNEL_CAP_EXCEEDED",
    "message": "Agent already at email cap (3/3). Close one or raise the cap before assigning another email conversation.",
    "status": 422,
    "details": { "channel": "email", "current": 3, "cap": 3 }
  },
  "meta": { "request_id": "req_3Jm7cR1v…", "timestamp": "2026-10-16T09:15:47.2Z" }
}
```

When the agent is opted out of the channel, `details` reads
`current: 0, cap: 0` and the message says the agent is not configured to
handle that channel.

**Reading the cap config.** The ceiling lives under **Settings → Team →
Channel concurrency caps**: one row per agent and channel
(`email`, `sms`, `whatsapp`, `voice`, …). A cap of 0 is an explicit opt-out
(the agent never receives that channel); any positive number is the
maximum conversations the agent holds on that channel at once. The count
behind `details.current` is the agent's live plate on that channel:
conversations that are open, active, or pending all occupy a slot; closed,
resolved, and snoozed ones free it.

**Free a slot.** Resolve or close one of the agent's active conversations
on the named channel (or resolve the ticket, for a ticket assign). The
slot frees as soon as the conversation leaves the live set; the next
assign then succeeds without any config change.

**Raise path.** Raise the ceiling for that agent and channel under
**Settings → Team → Channel concurrency caps** and retry the assign. The
gate re-reads the config on every assign, so the change takes effect
immediately, no re-sign-in or refresh needed.

**Spread-load path.** Assign to an agent with headroom instead. If several
agents share a channel, balance ceilings across the team rather than
raising one agent's cap; and let automated routing do the spreading for
you: routing rules and round-robin auto-assign only pick agents with spare
capacity on the channel, so a team with correct caps load-balances by
construction. A bulk assign fails with this code only when the explicitly
chosen assignee is at cap; splitting the batch across agents with headroom
clears it.

**Re-claim is safe.** Assigning a conversation the agent already owns does
not count against the cap, so re-claiming or re-saving an existing
assignment never trips this code; it fires only on net-new load.

**Do not confuse with** agent runtime errors
([Agent runtime errors](/troubleshooting/agent-errors)): this 422 is a
routing decision made before any AI agent runs, on human-agent capacity
you configured.

## `EMPTY_AUDIENCE_NOT_ALLOWED` — the campaign audience matches zero contacts

**Cause.** Campaign create names a segment or a list as the audience, and
that audience's current membership is 0. The guard reads the segment's
stored matched-contact count (or the list's member count) at create time
and refuses to mint a campaign that could never send.
`details.audience_type` tells you which kind of audience was probed,
`details.audience_id` which one, and `details.matched_count` is 0.

**Envelope sample.**

```json theme={null}
{
  "error": {
    "code": "EMPTY_AUDIENCE_NOT_ALLOWED",
    "message": "This segment matches 0 contacts. Update the segment's rules to match contacts, then create the campaign.",
    "status": 422,
    "details": {
      "audience_type": "segment",
      "audience_id": "seg_0193c4d1…",
      "matched_count": 0
    }
  },
  "meta": { "request_id": "req_5Wn9dK2s…", "timestamp": "2026-10-16T09:17:03.9Z" }
}
```

For a list audience the message reads "This list has 0 contacts. Add
contacts to the list before creating the campaign."

**Debug ladder.**

1. **Preview the count before create.** Open the audience first: under
   **Audience → Segments**, the segment panel shows the current
   matched-contact count; under **Audience → Lists**, the list shows its
   member count. That is the same count the create-time guard reads. If it
   shows 0, the 422 is correct and the fix is in the audience, not the
   campaign.
2. **Check the refresh cadence.** A segment re-evaluates its membership on
   a refresh cadence, distinct from a frozen cohort (see the Segment entry
   in the [Glossary](/reference/glossary)). If you just widened the rules,
   added tags, or imported contacts, the stored count may still reflect
   the previous evaluation: the guard sees the pre-refresh count even
   though more contacts now qualify. A list, by contrast, updates the
   moment members are added.
3. **Fix the audience.**
   * Segment: expand the rules (loosen a filter, add a tag or field
     condition that current contacts satisfy), or wait for the next
     refresh after a recent import and re-check the panel count.
   * List: add contacts, by import or manually, before create.
4. **Schedule the send after refresh.** If the audience only becomes
   non-empty at the next evaluation (for example, contacts qualify once a
   recent event lands), create the campaign after the segment panel shows
   a non-zero count, or schedule the send for after the refresh cadence
   has run. Creating against a still-empty segment always refuses.

**Do not confuse with** a send-time audience resolution failure
(`AUDIENCE_RESOLVE_FAILED`, a 502 on CRM-backed audiences): this 422 fires
before anything is scheduled, on your own Orbit segment or list, and is
always fixable from the audience side. See also
[Segment auto-suggest failures](/troubleshooting/ai-suggest-failed) for the
assisted segment-building surface.

## When to escalate

Bring the `meta.request_id` from the envelope plus:

1. **Your organization ID** — Settings → Organization, or `GET /api/v1/me`.
2. **For `SAFETY_CLASSIFIER_REFUSAL`**: the surface (prompt assist or trait
   auto-derive), the action you attempted, and why the content looks like
   a false positive.
3. **For `AGENT_CHANNEL_CAP_EXCEEDED`**: the agent, the channel, and the
   cap you see under Settings → Team, when you believe the count is wrong
   (the dashboard shows headroom but the assign still refused).
4. **For `EMPTY_AUDIENCE_NOT_ALLOWED`**: the segment or list id, when the
   audience panel shows a non-zero count yet create still refuses (a
   refresh that never ran).

## See also

* [Error Code Reference](/reference/error-codes) — definitions for every
  code routed here.
* [Glossary](/reference/glossary) — the Segment vs cohort membership
  semantics behind the refresh-cadence ladder.
* [Squad and conversation cost caps](/troubleshooting/squad-and-conversation-cost-caps) —
  the 429 spend-cap family on the AI side.
* [LLM upstream failures](/troubleshooting/llm-upstream-failures) — the
  transient 502 class on the AI side.
* [Agent runtime errors](/troubleshooting/agent-errors) — the agent
  execution error family.
* [Segment auto-suggest failures (`AI_SUGGEST_FAILED`)](/troubleshooting/ai-suggest-failed) —
  the segment-building surface's 502.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.