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.Section routing — which refusal fired
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.
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.
- Read
details.categoryanddetails.explanationfrom the envelope. - 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.
- Resubmit once revised. A clean payload completes normally.
- 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_idand the action you attempted; do not keep rewording blindly.
AGENT_DAILY_SPEND_CAP_REACHED and siblings, 429s with a retry_after,
covered in Squad and conversation cost caps)
or with opaque upstream failures (502s, covered in
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.
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): 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.
- 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.
- 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). 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.
- 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.
- 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.
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 for the
assisted segment-building surface.
When to escalate
Bring themeta.request_id from the envelope plus:
- Your organization ID — Settings → Organization, or
GET /api/v1/me. - 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. - 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). - 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 — definitions for every code routed here.
- Glossary — the Segment vs cohort membership semantics behind the refresh-cadence ladder.
- Squad and conversation cost caps — the 429 spend-cap family on the AI side.
- LLM upstream failures — the transient 502 class on the AI side.
- Agent runtime errors — the agent execution error family.
- Segment auto-suggest failures (
AI_SUGGEST_FAILED) — the segment-building surface’s 502.