Skip to main content

Documentation Index

Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt

Use this file to discover all available pages before exploring further.

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:
EndpointScopeRoles allowed
GET /voice:readany
POST /testvoice:readany
POST /, PATCH /:id, DELETE /:idvoice:writeowner / admin / developer

Intent object

FieldTypeNotes
idstringivri_<32 hex>. Stable across renames.
namestringLowercase alphanumeric + - / _, max 64 chars. Unique per tenant.
descriptionstringNatural-language prompt the classifier evaluates against the utterance (max 500 chars).
target_queue_idstring | nullACD queue UUID to route matched calls to.
target_agent_idstring | nullAI-agent id to route matched calls to. Mutually exclusive with target_queue_id at the app layer.
activebooleanInactive intents are excluded from classifier prompts. Default true.
createdAtstringISO-8601.
updatedAtstringISO-8601.
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).

List intents

GET /api/v1/voice/ivr-intents
Returns every intent for the calling tenant, ordered by createdAt 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. Only Postgres 42P01 (undefined_table) is swallowed; any other failure propagates as a normal error.
cURL
curl "https://api.orbit.devotel.io/api/v1/voice/ivr-intents" \
  -H "X-API-Key: dv_live_sk_your_key_here"
200 OK
{
  "data": [
    {
      "id": "ivri_3f1c8b2a9d4e5f7a8b6c1d2e3f4a5b6c",
      "name": "sales",
      "description": "Caller wants to buy a product or ask about pricing.",
      "target_queue_id": "que_sales_us",
      "target_agent_id": null,
      "active": true,
      "createdAt": "2026-05-22T10:30:00Z",
      "updatedAt": "2026-05-22T10:30:00Z"
    }
  ],
  "meta": { "request_id": "req_abc123", "timestamp": "2026-05-24T12:00:00Z" }
}

Create intent

POST /api/v1/voice/ivr-intents
Scope: voice:write. Role: owner / admin / developer.
name
string
required
Slug (lowercase alphanumeric + - / _), max 64 chars. Must be unique within the tenant.
description
string
required
Natural-language description fed to the classifier as the intent prompt. 1–500 chars.
target_queue_id
string
ACD queue UUID. Mutually exclusive with target_agent_id (enforced at the app layer; both may be omitted for analytics-only intents).
target_agent_id
string
AI-agent id (agt_*).
active
boolean
default:"true"
Whether the classifier should evaluate this intent on live calls.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/ivr-intents" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "billing",
    "description": "Caller has a question about an invoice, charge, or payment.",
    "target_queue_id": "que_billing_eu",
    "active": true
  }'
201 Created — returns the created row. Errors
Statuserror.codeCause
409IVR_INTENT_NAME_CONFLICTAn intent with this name already exists for the tenant.
422VALIDATION_ERRORname fails the regex, description is empty/too long, or both target_* keys are set.
500IVR_INTENT_CREATE_ERRORUnexpected DB error. Cause chain preserved in Sentry.

Update intent

PATCH /api/v1/voice/ivr-intents/{id}
Scope: 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
curl -X PATCH "https://api.orbit.devotel.io/api/v1/voice/ivr-intents/ivri_3f1c8b2a9d4e5f7a8b6c1d2e3f4a5b6c" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "active": false }'
200 OK — returns the updated row. 404 NOT_FOUND — id not found in the tenant schema.

Delete intent

DELETE /api/v1/voice/ivr-intents/{id}
Scope: 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
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/ivr-intents/ivri_3f1c8b2a9d4e5f7a8b6c1d2e3f4a5b6c" \
  -H "X-API-Key: dv_live_sk_your_key_here"
204 No Content on success. 404 NOT_FOUND — id not found.

Test classification

POST /api/v1/voice/ivr-intents/test
Ad-hoc classifier preview. Runs the supplied utterance against the tenant’s active intents and returns the matched intent (if any) plus the classifier’s confidence. Use this from the dashboard’s IVR studio to validate a new intent description without placing a real call.
utterance
string
required
Caller utterance to classify. Max 500 chars (utterances beyond this are truncated by the classifier).
language
string
default:"en"
IETF BCP-47 language tag (en, en-US, tr, es-419, …). Influences classifier prompt locale.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/ivr-intents/test" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "utterance": "I would like to ask about my last invoice",
    "language": "en"
  }'
200 OK
{
  "data": {
    "matched_intent_id": "billing",
    "confidence": 0.93,
    "reasoning": "Utterance explicitly mentions 'invoice', a direct billing concept.",
    "fallback": false
  },
  "meta": { "request_id": "req_abc123", "timestamp": "2026-05-24T12:00:00Z" }
}
When no active intent matches above the confidence threshold, matched_intent_id is null and fallback: true. An empty active intent set (no rows or all active: false) is a valid state — the classifier still runs and returns no_match. Performance: P95 < 1s (Haiku call ~250ms + intent fetch + audit log).

Error model

All errors follow the platform envelope:
{
  "error": {
    "code": "IVR_INTENT_NAME_CONFLICT",
    "message": "An intent with name 'sales' already exists.",
    "status": 409
  },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-05-24T12:00:00Z"
  }
}
Branch on 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.

See also