Troubleshooting: agent outbound-call and voice-clone guard codes
Three guard codes stop an agent call or a voice-clone request before any telephony or synthesis work begins. Each one blocks a different surface — the agent’s outbound-calling toggle, the agent’s guardrail envelope, and the voice-clone consent attestation — and each fails fast at the gate, so nothing downstream (a call record, a clone row, a synthesis credit) is created. This page tells you which gate fired, which surface to re-check, and when to escalate instead of retrying.Code matrix — symptom to fix
Outbound calling disabled
POST /api/v1/agents/:id/calls is the agent-originating outbound-call
endpoint. It walks a short chain of guards before it originates anything:
the agent must exist (AGENT_NOT_FOUND 404), must not be archived
(AGENT_ARCHIVED 400), and must have outbound calling switched on. The
OUTBOUND_CALLING_DISABLED 409 fires on the last check — the toggle is a
per-agent setting operators flip deliberately, and placing a call spends
balance, so the route refuses to originate for a disabled agent instead of
defaulting open.
Symptoms:
- The error message names one fix: enable the toggle.
- Your integration is fine; the failure is repeatable until the toggle flips, so a retry loop returns the same 409 every time.
- No call record, no billing event, no origination — the guard runs before any of those are touched.
GET, and carries the
agent’s default caller ID alongside the toggle — set it with a
caller_id field on the same PATCH body (send caller_id: null to clear
and fall back to the tenant default). Verify the agent id from
GET /api/v1/agents first; a bad id returns AGENT_NOT_FOUND, not this
code.
When to escalate instead: never for the toggle itself — it is a deliberate
per-agent control, tenant-owned end to end. Escalate only if the PATCH
route itself errors (a 4xx validation refusal, or a 5xx) or the toggle
reads back true from the GET while POST .../calls still returns the
409 — that mismatch narrows the window to the update you made, so capture
the PATCH response and one failing 409 envelope with its request id and
timestamps.
What NOT to try:
- Do not treat the 409 as a rate limit or a carrier block and back off exponentially — it fires identically until the config changes.
- Do not escalate a downstream guard (TCPA dialing window, balance, destination block) under this page — those have their own runbooks; this one owns only the pre-origination agent toggle.
Guardrail violation reserved code
GUARDRAIL_VIOLATION appears in the
error codes catalog with the status varies,
as a reserved AI-guardrail code. Reservation means the code is held in
the platform’s error-code enum for SDK stability and forward
compatibility — no public endpoint throws it today, and guardrail
failures still surface through other codes.
Confirmed facts:
- No public endpoint (
/chat,/invoke, graph test-run, voice-agent turn) throws it. Internal comments reference the stream path, but the code itself is unthrown — treat this page as triage. - Guardrail failures surface today as
AI_PARSE_FAILED(422) orVALIDATION_ERROR(422) on the/chat//invokeroutes — parse or schema mismatches in the agent’s output, not residual guardrail verdicts on the envelope. - The reserved code’s HTTP status varies — expected
422on/chatrun-and-validate,502when the provider loop degrades.
GUARDRAIL_VIOLATION response:
- Capture the exact HTTP status, the
messageanddetailsfields, and the request id off the envelope. - Check whether the response came through an SDK or a wrapper that remaps the model’s structured output — the raw envelope matters more than the mapped shape.
- Escalate with the raw envelope and the two request ids bracketing the change to support — this code is not supposed to surface on end-user calls today, so a 422 with it is a platform signal, not a tenant config issue.
- Do not write a fix branch that “handles”
GUARDRAIL_VIOLATIONon your /chat /invoke integration — the code is reserved, not thrown; the false guard branch is dead code. - Do not assume your agent is blocked by a guardrail because the code appears in the catalog — the catalog holds the reservation, not the live behavior.
Voice clone consent required
Every voice-clone call (POST /api/v1/voice/clones for call-sourced
cloning, POST /api/v1/voice/clones/upload for a sample upload, and any design or design-
upload flow that lands on the same gate) runs a consent gate first — the
attestation you send is validated before the route touches the database,
a provider, or any credit. The gate throws 422 with
VOICE_CLONE_CONSENT_REQUIRED when the attestation is missing or
incomplete, and that is a deliberate fail-fast, not a transient condition.
Attestation fields and the gate order:
biometric_consent_acknowledgedmust betrue— the acknowledgement that your application carries biometric-privacy obligations (BIPA / CUBI / EU AI Act Art 50) for a cloned voice. Missing it throws withmissing_field: "biometric_consent_acknowledged".voice_owner_relationshipmust be a non-empty classify string — the route accepts theself | employee | actor_with_release | customer_with_written_consent | othervocabulary; any other input throws withmissing_field: "voice_owner_relationship".- One of
voice_owner_consent_evidence_url(an HTTPS URL to the written evidence — ≥50 characters for the text variant) orvoice_owner_consent_text(a free-form attestation statement, ≥ 50 characters on a text-only attestation) must be present. Missing both throws withmissing_field: "voice_owner_consent_evidence_url | voice_owner_consent_text"; a text that is too short or too long throws withfield: "voice_owner_consent_text"plus the cap bound.
- The error is deterministic — missing attestation fields return it identically every time you retry.
- The gate passes before any clone row is created, so a correct retry is a genuine first attempt, not a duplicate; you are never charged for a blocked request.
- The route rejects both evidence text and evidence URL being absent — one of the two is required, and the attestation text cap is applied on both the length bounds.
source_call_id and on the upload
path with the sample. Resubmit with every required field populated and a
consent text at or above the length floor, or a HTTPS evidence URL that
points at the signed consent. The error’s details.missing_field (or
details.field on a length violation) names exactly which gate failed, so
there is one retry per field — not several blind ones.
When to escalate instead: never because of the gate itself — it is a
tenant-owned consent control, and the customer fixes it in the request.
Escalate if the API refuses every attestation shape you send, if the
details names a field the schema does not validate, or if a 5xx replaces
the 422 after a complete attestation — all of those narrow the fix to
the route layer, not to your compliance posture.
Triage table — fix or escalate
The first two codes are deliberate, tenant-facing gates — flipping the
right surface permanently retires them. The third is a reserved code that
maps no runnable path today.
What NOT to try
- Do not retry in a loop on the toggle code —
OUTBOUND_CALLING_DISABLEDfires identically on every request until the config changes; spend the backoff on the PATCH instead. - Do not write a fix branch for
GUARDRAIL_VIOLATION— reserved codes are placeholders, not endpoints; the false branch is dead code. - Do not shorten the attestation text below the floor to beat the length cap — that field names its bound; the gate validates exactly one thing at a time, and the retry must carry all the required fields.
See also
- Error codes reference — the catalog these
codes live in;
GUARDRAIL_VIOLATIONis recorded as reserved there. - Voice biometrics failures — the sidecar connect / auth / availability codes on enroll and verify this page does not own.
- The agent outbound-calling toggle on the dashboard — the same code the GET/PATCH route exposes: Voice agents → Agent → Outbound calling.
- Voice destination blocks — the post-toggle 4xx lane when the caller id or destination is what actually blocks the dial.