IVR Intents
Conversational IVR intents replace traditional DTMF menus (“Press 1 for sales”) with natural-language utterance classification. The caller says what they want; the platform runs the utterance through a per-tenant set of intent buckets and routes the call to the matching ACD queue or AI agent. Each intent is a named bucket (sales, billing, support, …) with a natural-language description that the NLU classifier (Claude Haiku) compares against the live caller utterance.
Base path: /api/v1/voice/ivr-intents
Authentication: Clerk session (Authorization: Bearer <token>) or API key (X-API-Key).
Scopes:
Intent object
Both
target_queue_id and target_agent_id may be null — that’s a valid pure-analytics intent (the call is classified for reporting but not routed).
DTMF-free speech menus
There are two ways to route on speech, and they compose:- Intent buckets (this page) — tenant-level descriptions the LLM classifier evaluates against the utterance. Best for open-ended routing across a whole tenant (“sales” vs “billing” vs “support”).
- Speech Input node grammar — per-node
speechIntentsphrases edited on the node itself. Matching is deterministic phrase-versus-transcript string math (normalised exact match, word-order tolerance, partial phrase coverage) — no classifier round trip runs in the live gather. Best for a handful of expected utterances at one menu point.
nomatch edge; set the confidenceThreshold node field alongside the tenant-level classifier threshold so a noisy line falls to a reprompt or an agent rather than a wrong branch.
Editing the phrases, the toggle, the provider, and the gate is documented in the grammar-editor section of Build and ship your first IVR flow.
List intents
GET /api/v1/voice/ivr-intentscreatedAt ASC. Includes inactive intents so the dashboard can render an enable/disable toggle.
Defense-in-depth fallback (DEVOTEL-ORBIT-5H): If the tenant’s 287_ivr_intents migration has not yet applied (new tenant whose just-in-time migration runner is still executing, or a restored-from-backup tenant), this endpoint returns an empty array data: [] rather than a 503. Four Postgres provisioning-gap SQLSTATEs are swallowed: 42P01 (undefined_table), 42703 (undefined_column), 3F000 (invalid_schema_name) and 42704 (undefined_object); any other failure propagates as a normal error. Transient connection blips (pgbouncer drain / Cloud SQL pool pressure — CONNECTION_CLOSED, ECONNRESET, 53300) are retried first (DEVOTEL-ORBIT-6D) and only degrade to the empty-list fallback if they are genuinely a provisioning gap.
cURL
Create intent
POST /api/v1/voice/ivr-intentsvoice:write. Role: owner / admin / developer.
string
required
Slug (lowercase alphanumeric +
- / _), max 64 chars. Must be unique within the tenant.string
required
Natural-language description fed to the classifier as the intent prompt. 1–500 chars.
string
ACD queue id (1–128 chars, e.g.
que_billing_eu). Mutually exclusive with target_agent_id (enforced at the app layer; both may be omitted for analytics-only intents).string
AI-agent id (
agt_*).boolean
default:"true"
Whether the classifier should evaluate this intent on live calls.
cURL
Update intent
PATCH /api/v1/voice/ivr-intents/{id}voice:write. Role: owner / admin / developer.
Partial update. Only fields present in the body overwrite the existing row. Send target_queue_id: null to clear a route.
cURL
Delete intent
DELETE /api/v1/voice/ivr-intents/{id}voice:write. Role: owner / admin / developer.
Hard delete. The intent will no longer appear in any future classifier prompt or list response. Existing call routings already in flight are not affected (routing decisions are recorded on call_logs at decision time, not by FK reference).
cURL
Test classification
POST /api/v1/voice/ivr-intents/teststring
required
Caller utterance to classify. Max 500 chars (utterances beyond this are truncated by the classifier).
string
default:"en"
IETF BCP-47 language tag (
en, en-US, tr, es-419, …). Influences classifier prompt locale.cURL
fallback_reason is absent. When no active intent matches above the confidence threshold (0.7), matched_intent_id is null and fallback_reason carries one of "low_confidence" (a candidate scored below threshold), "no_match" (no plausible mapping), or "ambiguous" (two or more intents tied). There is no boolean fallback field. An empty active intent set (no rows or all active: false) is a valid state — the classifier still runs and returns fallback_reason: "no_match":
Error model
All errors follow the platform envelope:error.code, not error.message (messages may be localised).
Observability
Every state change (create, update, delete, test) writes a structured audit log with action=voice.ivr_intent.<verb> and the resource id, available via the Audit Logs API.
Speech-gather grammar (DTMF-free routing, NLU-tuned)
The tenant intent buckets above are one way to route by speech — a classifier decision per utterance. For menus that need a deterministic, no-LLM match and true DTMF-free operation, the flow’s Speech node (speechInput) also accepts an inline grammar of caller phrases: matching is exact phrase matching against the transcript, not a model call, so the same phrase routes the same way on every call.
Declare the grammar on the Speech node’s data under speechIntents; each intent’s name is the branch handle you wire out of the node:
Edge precedence at the Speech node: a pressed digit routing to a wired
dtmfKey wins; otherwise the transcript is matched against the phrases; no match (or confidence below the threshold) takes the nomatch branch, then default; a silent caller retries within the gather’s retry budget and then takes the timeout branch, then default.
Pick the inline grammar for short, closed menus (“press or say”) and the tenant intent buckets for open-ended requests — the two can share one flow (guide). Turn on detectLanguage on the Speech node to pin a multilingual caller’s language for the rest of the call.
See also
- Build and ship your first IVR flow — grammar editor for a node’s speech intents, with a worked DTMF-free menu
- Voice API overview
- Voice Quickstart
- Build an IVR flow with NLU intent routing
- Build your first IVR flow
- Webhook events — call lifecycle