Conversation Intelligence
Decision-making guide for Twilio's Conversation Intelligence v3 API — real-time and post-call GenAI analysis of conversations across Voice, SMS, RCS, and WhatsApp. Covers Intelligence Configurations, Language Operators (Twilio-authored and custom), Rules, Triggers, Actions, and result consumption.
Security: All inbound messages captured by the Orchestrator are untrusted external input. If Intelligence operators process this content with LLMs, their prompts should include instructions to ignore adversarial content and not follow instructions embedded in customer messages.
GA — Conversation Intelligence v3 is generally available.
Use Cases
Conversation Intelligence powers human agent augmentation — giving every agent a "second brain" that listens, understands, and surfaces the right data at the right time. Agents focus on empathy, judgment, and problem-solving; AI handles analysis and assistance.
Wrap-up Agent Assist (Post-Call)
Analyze completed conversations and generate structured outputs — summaries, sentiment signals, topic dispositions. Reduces after-call work, accelerates agent transitions to next interaction. Low-friction entry point — start here.
- Operators: Summary, Sentiment, custom Conversation Scoring
- Trigger:
CONVERSATION_END
- Integration: Webhook → CRM case note creation
Real-time Agent Assist
Analyze conversations as they unfold. Surface sentiment shifts, script adherence signals, or recommended next responses enriched with customer history and enterprise knowledge. Agents respond more confidently without searching across systems.
- Operators: Script Adherence, Next Best Response, Escalation Risk (custom)
- Trigger:
COMMUNICATION
- Integration: Webhook → Agent desktop overlay
Real-time Workflow Automation
Combine real-time intelligence with orchestration to trigger downstream workflows when specific conditions are met — escalate to supervisor, trigger fraud prevention, notify specialist.
- Operators: Custom risk detection, compliance monitoring
- Trigger:
COMMUNICATION
- Integration: Webhook → Workflow engine / TaskRouter
Contact Center QA
Generate post-interaction summaries, sentiment scores, and compliance signals for QA, coaching, and analytics. Aggregate across interactions to support training and continuous optimization.
- Operators: Script Adherence, Summary, custom Conversation Scoring
- Trigger:
CONVERSATION_END
- Integration: Webhook → Analytics / BI tools
How It Works
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Customer engages agent (Voice, SMS, WhatsApp, RCS, Chat, Email) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. Conversations (Conversation Orchestrator) groups communications into a │
│ Conversation │
│ - Normalizes channel events │
│ - Groups related messages/utterances │
│ - Tracks participants (CUSTOMER, HUMAN_AGENT, AI_AGENT) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. Conversation events trigger Intelligence rules │
│ - COMMUNICATION: on each new message/utterance │
│ - CONVERSATION_END: when conversation closes │
│ - CONVERSATION_INACTIVE: when conversation goes idle │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. Language Operators analyze the conversation │
│ - Twilio-authored: Sentiment, Summary, NBR, Script Adherence │
│ - Custom: domain-specific analysis with your prompts │
│ - Context: enriched with Customer Memory + Enterprise Knowledge │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. Results delivered via webhook + REST API │
│ - Real-time: Agent desktop, workflow triggers │
│ - Post-call: CRM notes, QA systems, analytics │
│ - Aggregated: Conversational Insights for cross-conversation analysis │
└─────────────────────────────────────────────────────────────────────────────┘
Key insight: Real-time and post-conversation intelligence use the same underlying model. Start with low-friction post-call summaries, then progressively introduce real-time assist using the same components.
Scope
CAN
- Analyze conversations in real-time (per-message) and post-conversation (at close/inactive) via Language Operators
- Use 4 Twilio-authored operators: Sentiment, Summary, Next Best Response, Script Adherence
- Create custom Language Operators with natural language prompts and structured output (TEXT, JSON, CLASSIFICATION) —
EXTRACTION is a read-only format returned by some Twilio-authored operators; it cannot be set on custom operators you create
- Define up to 5 rules per Intelligence Configuration, each with 1-5 operators (minimum 1 required), 0 or 1 trigger, and 0-2 webhook actions
- Throttle real-time triggers with
count parameter (run every N communications, min 1, max 20)
- Deliver results via webhook (POST) and query historically via REST API
- Track conversations across SMS, Voice, RCS, WhatsApp, Chat, and Email channels via Conversation Orchestrator (Conversations v2) integration
- Create custom operators with parameters (
{{parameters.name}} syntax), including knowledge base references (KNOWLEDGE_BASE_AND_SOURCE_IDS type — value format: knowledge_base_id:knowledge_source_id)
- Enrich operators with Customer Memory (
context.memory.enabled: true) and Enterprise Knowledge (context.knowledge.bases: [...]) at the rule or operator level
- Add
trainingExamples (input/output pairs) to custom operators to improve accuracy
- Pin a specific operator version in a rule via
operators[].version; omit to use latest
- Query OperatorResults filtered by
intelligenceConfigurationId, conversationId, or operatorId
- Query operator versions and fetch specific version details
- Delete Intelligence Configurations, custom Operators, and individual OperatorResults via REST API
- Use ETag/If-Match headers for optimistic locking on Operator updates (returns 412 on mismatch) — ETag is not supported on Configuration updates
- Filter Conversations by
status, channels, createdAtBefore/createdAtAfter, channelId, intelligenceConfigurationIds, operatorIds
- Authenticate with both Account SID/Auth Token and API Key/Secret
- Define rules without a trigger (trigger is optional per spec; runs on all events if omitted)
CANNOT
- JSON-only API — All v3 endpoints require
Content-Type: application/json. Form-encoded bodies return HTTP 415 with error 20422.
- No standalone operation — v3 requires Conversation Orchestrator (Conversations v2) for conversation capture. You cannot feed raw messages or recordings into v3 directly.
- No per-message sentiment — Sentiment is conversation-level, accumulating across all messages. A conversation with one positive and one negative message returns "mixed", not separate results per message.
- No deleting Twilio-authored operators — DELETE returns 404 "Operator not found" for Twilio-authored operators, not 403. They are not treated as "yours" to delete.
- No editing Twilio-authored operator prompts — Twilio-authored operators have
prompt: null when retrieved via GET. The prompt is hidden and not configurable.
- No more than 2 actions per rule — The API enforces
size must be between 0 and 2 for actions.
- No more than 5 rules per configuration — API enforces
size must be between 0 and 5.
- No PCI or HIPAA compliance — Conversation Intelligence v3 is not PCI compliant or HIPAA Eligible. Do not use for payment data or protected health information.
- No GET/PUT/DELETE on v3 Conversations — The Conversations endpoint is read-only (GET list, GET by ID). Conversation lifecycle is controlled by Conversation Orchestrator, not by the Intelligence API.
- No unsupported JSON schema features — The following are rejected in
outputSchema: minLength/maxLength (strings), patternProperties (objects), uniqueItems (arrays). Use basic types only.
- Cannot use PUT to update a live configuration — PUT creates an inactive version with no activation API. Operators silently stop returning results. Workaround: DELETE the configuration and POST to recreate it.
- Silent Memory Store linkage failures — If
memoryStoreId points to a deleted or invalid store, capture still works but identity resolution and extraction silently fail with no error. Implement periodic health checks to verify Memory Store linkage is functioning.
Quick Decision
| Need |
Use |
Why |
| Real-time agent assist during live calls/chats |
v3 + COMMUNICATION trigger + Next Best Response operator |
Real-time webhook delivery per utterance |
| Post-call QA scoring |
v3 + CONVERSATION_END trigger + Script Adherence operator |
Runs once at conversation close, returns detailed score |
| Conversation sentiment tracking |
v3 + Sentiment operator (Twilio-authored) |
Conversation-level classification: positive/negative/neutral/mixed |
| Post-call recording transcription + analysis |
v2 Voice Intelligence (existing) |
v3 does not ingest recordings directly — v2 pipeline handles recording→transcript→operators |
| Custom domain-specific analysis |
v3 + Custom operator with JSON output |
Define prompt, parameters, structured output schema |
| Cross-channel conversation history |
Conversations v2 (Conversation Orchestrator) alone |
v3 adds analysis on top; Conversation Orchestrator handles capture and history |
| Simple keyword extraction |
v3 + Custom operator (EXTRACTION format) |
Structured extraction with custom prompt |
Decision Frameworks
v2 (Voice Intelligence) vs v3 (Conversation Intelligence)
| Dimension |
v2 (Voice Intelligence) |
v3 (Conversation Intelligence) |
| Input source |
Recording SIDs (audio) |
Conversation Orchestrator conversations (text/transcriptions) |
| Channels |
Voice only |
SMS, Voice, RCS, WhatsApp, Chat, Email |
| Operator management |
Console only (attach to Intelligence Service) |
REST API (full CRUD on custom operators) |
| Trigger model |
Post-transcription (async) |
Real-time (per-message) or post-conversation |
| Result delivery |
Webhook (voice_intelligence_transcript_available) |
Webhook (per-rule action) + REST query |
| SDK support |
client.intelligence.v2.transcripts |
Twilio Node.js SDK supported |
| SID prefix |
GA (service), GT (transcript), LY (operator) |
intelligence_configuration_*, intelligence_operator_* |
| Status |
GA |
GA |
| Coexistence |
Works alongside v3 |
Works alongside v2 |
Use v2 when: You need post-call transcription from recordings, or need GA stability.
Use v3 when: You need real-time analysis, cross-channel support, or API-managed custom operators.
Real-Time vs Post-Conversation
| Factor |
COMMUNICATION trigger |
CONVERSATION_END trigger |
CONVERSATION_INACTIVE trigger |
| When it fires |
On each new message/utterance |
When conversation closes |
When conversation goes idle |
| Latency |
Near real-time |
Seconds after close |
After inactive timeout |
| Use cases |
Agent assist, escalation detection, live compliance |
QA scoring, summaries, CRM updates |
Idle conversation follow-up |
| Operator context |
Accumulating — sees all messages so far |
Complete conversation |
Messages up to inactivity point |
| Throttling |
count parameter (every N messages) |
N/A |
N/A |
| Cost implication |
Runs per message (more executions) |
Runs once per conversation |
Runs once per inactivity event |
Operator Version Lifecycle
| Status |
Behavior |
When |
PREVIEW |
Normal execution, restricted visibility |
Internal/testing versions |
ACTIVE |
Normal execution, full availability |
Production-ready versions |
DEPRECATED |
Executes with Warn event via Watch |
Migration window — update to newer version |
RETIRED |
Hard failure, Error logged in Watch |
Must update Intelligence Configuration manually |
Custom Operator Output Formats
| Format |
Use When |
Result Shape |
Schema Support |
| TEXT |
Free-form analysis, summaries, translations |
{"text": "..."} |
Auto-generated (not customizable) |
| JSON |
Structured extraction with custom fields |
User-defined via outputSchema |
Full JSON Schema (max 100 props, 10 nesting levels, 1000 enum values max) |
| CLASSIFICATION |
Category labeling (sentiment, intent, topic) |
{"label": "..."} |
Auto-generated |
| EXTRACTION |
Returned by some Twilio-authored operators only — cannot be set on custom operators |
{"entities": [{"text": "...", "label": "..."}]} |
N/A (read-only) |
Twilio-Authored Operator Reference
Ready-to-use operators maintained by Twilio. Use these IDs directly in rules — no custom prompt required.
| Operator |
ID |
Best Trigger |
Use Case |
| Sentiment |
intelligence_operator_01kcrvw16kfa88qvgrfmr7y151 |
COMMUNICATION |
Real-time sentiment tracking (positive/negative/neutral/mixed) |
| Summary |
intelligence_operator_01kcv35pnkeysaf6z6cqtbpegn |
CONVERSATION_END |
Post-call conversation summary |
| Next Best Response |
intelligence_operator_01kea27sy7ffsafmtsfp17nzx4 |
COMMUNICATION |
Real-time agent assist with suggested responses |
| Script Adherence |
intelligence_operator_01kf34tcyefpyb1t4m0nbd8rxg |
CONVERSATION_END |
QA scoring for script compliance |
Note: Twilio-authored operators have author: "TWILIO" and prompt: null when retrieved via GET. Prompts are hidden and not configurable. Use custom operators if you need control over the prompt.
Integration Patterns
Code samples use raw fetch() for clarity, but the Twilio Node.js SDK is also supported for v3.
Authentication Helper
const INTELLIGENCE_V3_BASE = 'https://intelligence.twilio.com/v3';
function getAuthHeaders() {
const credentials = Buffer.from(
`${process.env.TWILIO_ACCOUNT_SID}:${process.env.TWILIO_AUTH_TOKEN}`
).toString('base64');
return {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
};
}
Create Intelligence Configuration with Rules
// Step 1: Create configuration (empty rules initially)
const configResponse = await fetch(
`${INTELLIGENCE_V3_BASE}/ControlPlane/Configurations`,
{
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
displayName: 'Customer Support Analytics',
description: 'Real-time sentiment + post-call summary',
rules: [],
}),
}
);
const config = await configResponse.json();
// config.id = "intelligence_configuration_..."
// Step 2: Add rules via PUT (replaces all rules)
const updateResponse = await fetch(
`${INTELLIGENCE_V3_BASE}/ControlPlane/Configurations/${config.id}`,
{
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify({
displayName: 'Customer Support Analytics',
rules: [
{
operators: [
{ id: 'intelligence_operator_01kcrvw16kfa88qvgrfmr7y151' }, // Sentiment
],
triggers: [{ on: 'COMMUNICATION' }],
actions: [
{ type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/realtime-results' },
],
},
{
operators: [
{ id: 'intelligence_operator_01kcv35pnkeysaf6z6cqtbpegn' }, // Summary
],
triggers: [{ on: 'CONVERSATION_END' }],
actions: [
{ type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/post-call-results' },
],
},
],
}),
}
);
const updatedConfig = await updateResponse.json();
// updatedConfig.version = 2 (auto-incremented)
Link to Conversation Orchestrator Conversation Configuration
// Intelligence config must be linked to a Conversation Orchestrator conversation config
// See conversation-orchestrator skill for full Conversation Orchestrator setup
const convConfigResponse = await fetch(
'https://conversations.twilio.com/v2/ControlPlane/Configurations',
{
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
displayName: 'Support Config',
memoryStoreId: 'mem_store_...', // Required — create via Memory API first
conversationGroupingType: 'GROUP_BY_PARTICIPANT_ADDRESSES',
intelligenceConfigurationIds: [config.id],
channelSettings: {
SMS: {
statusTimeouts: { inactive: 5, closed: 10 },
captureRules: [{ from: '*', to: '+1XXXXXXXXXX', metadata: {} }],
},
},
}),
}
);
Consume Operator Results
// Query all results for an intelligence configuration
const resultsResponse = await fetch(
`${INTELLIGENCE_V3_BASE}/OperatorResults?intelligenceConfigurationId=${config.id}`,
{ headers: getAuthHeaders() }
);
const results = await resultsResponse.json();
for (const operatorResult of results.items) {
console.log(`Operator: ${operatorResult.operator.id}`);
console.log(`Format: ${operatorResult.outputFormat}`);
console.log(`Payload: ${JSON.stringify(operatorResult.result)}`); // e.g. { text: "..." } or { label: "..." }
console.log(`Conversation: ${operatorResult.conversationId}`);
console.log(`Trigger: ${operatorResult.executionDetails.trigger.on}`);
// Context that was actually used at runtime (single source of truth):
console.log(`Memory profile: ${operatorResult.executionDetails.resolvedContext?.memory?.profileId}`);
console.log(`Knowledge sources: ${JSON.stringify(operatorResult.executionDetails.resolvedContext?.knowledge?.sources)}`);
// Cost/perf metadata:
console.log(`Model: ${operatorResult.metadata.system.resolvedModel}, latencyMs: ${operatorResult.metadata.system.latencyMs}`);
}
Paginate Through Results
All list endpoints (/OperatorResults, /Conversations, /Operators, /Configurations) use cursor-based pagination. Default page size is 50; maximum is 1000.
async function* getAllOperatorResults(configId) {
let pageToken = undefined;
do {
const url = new URL(`${INTELLIGENCE_V3_BASE}/OperatorResults`);
url.searchParams.set('intelligenceConfigurationId', configId);
url.searchParams.set('pageSize', '1000');
if (pageToken) url.searchParams.set('pageToken', pageToken);
const response = await fetch(url, { headers: getAuthHeaders() });
const data = await response.json();
yield* data.items;
pageToken = data.meta?.nextToken; // null/undefined when no more pages
} while (pageToken);
}
// Usage:
for await (const result of getAllOperatorResults(config.id)) {
console.log(result.operator.id, result.result);
}
Enable Customer Memory and Enterprise Knowledge on a Rule
// Context is configured at the rule level (not the operator level)
rules: [
{
operators: [{ id: 'intelligence_operator_01kea27sy7ffsafmtsfp17nzx4' }], // NBR
triggers: [{ on: 'COMMUNICATION' }],
actions: [{ type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/nbr' }],
context: {
memory: { enabled: true }, // inject customer profile from Memory Store
knowledge: {
bases: ['knowledge_base_id_here'], // inject enterprise KB articles
},
},
},
]
Create a Custom Operator with Training Examples and Parameters
const operatorResponse = await fetch(
`${INTELLIGENCE_V3_BASE}/ControlPlane/Operators`,
{
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
displayName: 'Escalation Risk Detector',
prompt: `Analyze this conversation between a customer and agent.
Product context: {{parameters.productName}}
Classify escalation risk as LOW, MEDIUM, or HIGH based on customer frustration signals.`,
outputFormat: 'CLASSIFICATION',
parameters: {
productName: { type: 'STRING', required: true, description: 'Product line being discussed' },
// KNOWLEDGE_BASE_AND_SOURCE_IDS parameters are passed as "kb_id:source_id" at rule time
knowledgeContext: { type: 'KNOWLEDGE_BASE_AND_SOURCE_IDS', required: false },
},
trainingExamples: [
{
input: 'Customer: This is the third time I have called about this issue',
output: 'HIGH',
},
{
input: 'Customer: Thanks, I think that might work',
output: 'LOW',
},
],
}),
}
);
const operator = await operatorResponse.json();
// Pin this operator to a specific version in your rule:
// operators: [{ id: operator.id, version: operator.version }]
Gotchas
Setup
Memory Store is required for Conversation Orchestrator: You cannot create a Conversations v2 Configuration without a memoryStoreId. The Memory API returns "memoryStoreId: must not be null" (error 20001). Create the Memory Store first via POST memory.twilio.com/v1/ControlPlane/Stores.
JSON-only API: All v3 endpoints require Content-Type: application/json. Form-encoded bodies return HTTP 415 with error 20422 ("does not support this payload format"). This matches Conversation Orchestrator but differs from most Twilio APIs.
v2 and v3 coexist independently: Creating v3 configurations does not affect v2 Intelligence Services (GA* SIDs). Both are accessible on the same account simultaneously. They share the intelligence.twilio.com host but use different URL paths (/v2/Services vs /v3/ControlPlane/Configurations).
Configuration
PUT creates an inactive version — operators stop returning results: When you PUT to update an Intelligence Configuration, the new version is created in an inactive state. There is no activation API to make it live. Your operators will silently stop producing results. Workaround: DELETE the configuration and POST to recreate it with the updated rules/operators. This is the only reliable way to update a live configuration.
PUT replaces all rules: Updating a configuration replaces the entire rules array. There is no PATCH or per-rule update. Always include all rules in the PUT body, not just the changed one.
Config version auto-increments: Each PUT bumps the version field. Conversation Orchestrator conversation configs use version for optimistic locking, but Intelligence configs accept PUT without version checks.
Rules get their own IDs: When rules are created via PUT, each gets an auto-generated ID (intelligence_configurationrule_*). These IDs appear in OperatorResult references but are not user-settable.
Trigger count parameter throttles execution: Setting {"on":"COMMUNICATION","parameters":{"count":3}} runs the operator every 3 messages instead of every message.
Runtime
Dual capture rules cause duplicate processing: If both inbound (from: *, to: +1XXX) and outbound (from: +1XXX, to: *) capture rules match the same SMS, Conversation Orchestrator creates two Communications for one message, and Intelligence produces two OperatorResults. Use unidirectional capture rules unless you specifically want both.
Twilio number is auto-typed HUMAN_AGENT: In Conversation Orchestrator conversations, the Twilio number is automatically assigned type: "HUMAN_AGENT" and the external number gets type: "CUSTOMER" with automatic memory profile resolution (mem_profile_*).
Sentiment accumulates across messages: The Sentiment operator analyzes the full conversation context, not individual messages. After a positive message, sentiment was "positive". After adding a negative message to the same conversation, it became "mixed".
Near real-time delivery for COMMUNICATION trigger: Results are delivered via webhook shortly after each utterance — the full pipeline is Conversation Orchestrator capture → Intelligence trigger → Operator execution → webhook delivery.
Custom operator TEXT output auto-wraps: Custom operators with outputFormat: "TEXT" always return {"text": "..."} regardless of the prompt. The outputSchema is auto-generated and not customizable for TEXT format.
Observability
conversationConfigurationId returns "unused": The v3 Conversations endpoint returns "conversationConfigurationId": "unused" instead of the actual Conversation Orchestrator config ID. Use intelligenceConfigurationIds array instead for linking.
No isTwilioAuthored field: Twilio-authored operators are distinguished by author: "TWILIO", custom operators by author: "SELF". There is no boolean isTwilioAuthored field.
Twilio-authored operator prompts are hidden: GET on a Twilio-authored operator returns prompt: null. You cannot inspect or modify the system prompt. Custom operators return the full prompt.
Two separate metadata sections: OperatorResults carry both metadata.system and executionDetails.resolvedContext — they serve different purposes:
metadata.system: cost and performance — resolvedModel (LLM used), latencyMs, inputCharacters/outputCharacters (billing units), inputTruncated
executionDetails.resolvedContext: what context was actually injected at runtime — memory (profileId, memoryStoreId) and knowledge (sources: array of {baseId, sourceId}). This is the single source of truth for context resolution.
Error Handling
Error codes are consistent: v3 uses Twilio standard error codes: 20001 (bad request/validation), 20404 (not found), 20422 (unsupported format), 70001 (operator validation). All include userError: true and descriptive messages.
Invalid operator ID gives specific error: Using a non-existent operator ID in a rule returns 400 with code 70001 and message identifying the exact invalid operator.
JSON Schema
All JSON schema fields are required by default — and Twilio auto-sets this: Twilio automatically sets additionalProperties: false and marks all provided fields as required in outputSchema. Do not add required or additionalProperties yourself — Twilio overwrites any values you provide. The practical consequence: if the LLM cannot populate a field, the operator execution fails. Use union types for nullable fields: "type": ["string", "null"].
Nullable field pattern: To make a field optional/nullable in JSON output, use array type union:
{
"outputSchema": {
"type": "object",
"properties": {
"requiredField": { "type": "string" },
"optionalField": { "type": ["string", "null"] }
}
}
}
This allows the operator to return null for fields where the LLM has insufficient context.
- Unsupported JSON schema features cause silent or hard failures: The following JSON Schema features are NOT supported in
outputSchema and will be rejected. Stick to type, enum, properties, items, anyOf, $defs/$ref.
- Strings:
minLength, maxLength
- Objects:
patternProperties, unevaluatedProperties, propertyNames, minProperties, maxProperties
- Arrays:
unevaluatedItems, contains, minContains, maxContains, uniqueItems
executionDetails.context was removed: Older docs and some live responses may show executionDetails.context. This field was removed in a breaking change. Use executionDetails.resolvedContext — it contains memory (profileId, memoryStoreId) and knowledge (array of baseId/sourceId pairs).
Operator result query param is intelligenceConfigurationId (not intelligenceConfiguration): The REST API filter parameter for listing OperatorResults by config is intelligenceConfigurationId. Using the shorter form returns unfiltered results.
KNOWLEDGE_BASE_AND_SOURCE_IDS parameter values must use colon-separated format: When passing a knowledge base parameter to an operator at rule time, the value must be formatted as "knowledge_base_id:knowledge_source_id". Passing just the KB ID or using any other separator returns a validation error. Only plaintext KB sources are supported.
KNOWLEDGE_BASE_AND_SOURCE_IDS parameters do not support default: Unlike STRING, INTEGER, NUMBER, and BOOLEAN parameter types which all allow a default value, KNOWLEDGE_BASE_AND_SOURCE_IDS does not. Defining a default on a KB parameter will be ignored or rejected.
Each rule requires at least 1 operator: The operators array on a rule has minItems: 1. Submitting a rule with an empty operators array on create or update returns a 400 validation error.
List endpoints are paginated — don't assume you got all results: All list endpoints (/OperatorResults, /Conversations, /Operators, /Configurations) return a max of 50 items by default (max 1000 with pageSize). The response meta.nextToken is non-null when more pages exist. Always paginate when querying production data sets.
Conversational Insights (Cross-Conversation Analytics)
Where the Intelligence API gives you per-conversation OperatorResults, the Insights API v3 is the query layer for aggregating across thousands of conversations — grouping, filtering, and counting by dimensions like sentiment, channel, language, and operator output.
Base URL: https://insights.twilio.com
Public Beta — Insights v3 is currently in public beta. The query schema is subject to change.
When to Use Insights vs Intelligence REST API
| Goal |
Use |
| Get the result for a specific conversation |
Intelligence API: GET /v3/OperatorResults?conversationId=... |
| Count conversations by sentiment over time |
Insights API: query with OperatorResult.Value dimension |
| Find all conversations where agent went off-script |
Insights API: filter on OperatorResult.Value |
| Build a sentiment trend dashboard |
Insights API: group by DateCreated + OperatorResults |
| Discover available metrics and dimensions |
Insights API: GET /v3/InsightsDomains/Conversations/Metadata |
Endpoints
| Method |
Path |
Purpose |
POST |
/v3/InsightsDomains/Conversations/Query |
Execute a semantic query, returns first page |
GET |
/v3/InsightsDomains/Conversations/Query?pageToken=... |
Fetch subsequent pages |
GET |
/v3/InsightsDomains/Conversations/Metadata |
Discover available cubes, measures, dimensions |
Same Basic Auth as Intelligence API. JSON-only (Content-Type: application/json).
Query a Sentiment Distribution
const INSIGHTS_BASE = 'https://insights.twilio.com';
const response = await fetch(
`${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query`,
{
method: 'POST',
headers: getAuthHeaders(), // same helper as Intelligence API
body: JSON.stringify({
domain: 'Conversations',
query: {
measures: ['Conversation.Count'],
dimensions: ['OperatorResults', 'Channels', 'DateCreated'],
filters: [{
op: 'AND',
expressions: [
{ op: 'IN', field: 'OperatorResult.Value', values: ['positive', 'negative'] },
],
}],
orderBy: [{ field: 'OperatorResults.CreatedDate', direction: 'DESC' }],
},
}),
}
);
const data = await response.json();
// data.items = [{ Id: 'conv1', OperatorResults: 'positive', Channels: ['voice'], ... }]
// data.meta.nextToken — use for next page (null if last page)
Query fields:
| Field |
Description |
query.measures |
What to aggregate — e.g. "Conversation.Count", "OperatorResult.Count" |
query.dimensions |
What to group by — e.g. "OperatorResults", "Channels", "Languages", "DateCreated" |
query.filters |
Nested filter tree with op + expressions. Filter ops: AND, OR, EQ, NE, GT, LT, IN |
query.orderBy |
Sort by field + ASC/DESC |
Pagination
POST returns the first page. Subsequent pages use GET with pageToken. Stop when meta.nextToken is null.
async function* queryAll(queryBody) {
const first = await fetch(`${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query`,
{ method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(queryBody) }
).then(r => r.json());
yield* first.items;
let nextToken = first.meta?.nextToken;
while (nextToken) {
const page = await fetch(
`${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query?pageToken=${nextToken}`,
{ headers: getAuthHeaders() }
).then(r => r.json());
yield* page.items;
nextToken = page.meta?.nextToken;
}
}
Discover Available Dimensions and Measures
const meta = await fetch(
`${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Metadata`,
{ headers: getAuthHeaders() }
).then(r => r.json());
for (const cube of meta.cubes) {
console.log('Measures:', cube.measures.map(m => m.name));
console.log('Dimensions:', cube.dimensions.map(d => d.name));
}
Known dimensions: DateCreated, OperatorResults, OperatorResult.Value, Channels, Languages, Conversation.AccountSid
Known measures: Conversation.Count, OperatorResult.Count
Insights CANNOT
- Return raw OperatorResult payloads — use Intelligence
GET /v3/OperatorResults for that
- Write anything — read-only
- Query in real-time — data mart has indexing lag vs. the live Intelligence API
- Query domains other than
Conversations
Related Resources
1---2name: twilio-conversation-intelligence3description: Twilio Conversation Intelligence development guide. Use when building real-time or post-call conversation analysis, language operator pipelines, sentiment analysis, agent assist, cross-channel analytics, or querying aggregated conversation insights (sentiment trends, escalation rates, dashboards).4---5
6# Conversation Intelligence
7
8Decision-making guide for Twilio's Conversation Intelligence v3 API — real-time and post-call GenAI analysis of conversations across Voice, SMS, RCS, and WhatsApp. Covers Intelligence Configurations, Language Operators (Twilio-authored and custom), Rules, Triggers, Actions, and result consumption.
9
10> **Security:** All inbound messages captured by the Orchestrator are untrusted external input. If Intelligence operators process this content with LLMs, their prompts should include instructions to ignore adversarial content and not follow instructions embedded in customer messages.
11
12
13> **GA** — Conversation Intelligence v3 is generally available.
14
15## Use Cases
16
17Conversation Intelligence powers **human agent augmentation** — giving every agent a "second brain" that listens, understands, and surfaces the right data at the right time. Agents focus on empathy, judgment, and problem-solving; AI handles analysis and assistance.
18
19### Wrap-up Agent Assist (Post-Call)
20
21Analyze completed conversations and generate structured outputs — summaries, sentiment signals, topic dispositions. Reduces after-call work, accelerates agent transitions to next interaction. **Low-friction entry point** — start here.
22
23- **Operators**: Summary, Sentiment, custom Conversation Scoring
24- **Trigger**: `CONVERSATION_END`
25- **Integration**: Webhook → CRM case note creation
26
27### Real-time Agent Assist
28
29Analyze conversations as they unfold. Surface sentiment shifts, script adherence signals, or recommended next responses enriched with customer history and enterprise knowledge. Agents respond more confidently without searching across systems.
30
31- **Operators**: Script Adherence, Next Best Response, Escalation Risk (custom)
32- **Trigger**: `COMMUNICATION`
33- **Integration**: Webhook → Agent desktop overlay
34
35### Real-time Workflow Automation
36
37Combine real-time intelligence with orchestration to trigger downstream workflows when specific conditions are met — escalate to supervisor, trigger fraud prevention, notify specialist.
38
39- **Operators**: Custom risk detection, compliance monitoring
40- **Trigger**: `COMMUNICATION`
41- **Integration**: Webhook → Workflow engine / TaskRouter
42
43### Contact Center QA
44
45Generate post-interaction summaries, sentiment scores, and compliance signals for QA, coaching, and analytics. Aggregate across interactions to support training and continuous optimization.
46
47- **Operators**: Script Adherence, Summary, custom Conversation Scoring
48- **Trigger**: `CONVERSATION_END`
49- **Integration**: Webhook → Analytics / BI tools
50
51## How It Works
52
53```
54┌─────────────────────────────────────────────────────────────────────────────┐
55│ 1. Customer engages agent (Voice, SMS, WhatsApp, RCS, Chat, Email) │
56└─────────────────────────────────────────────────────────────────────────────┘
57 │
58 ▼
59┌─────────────────────────────────────────────────────────────────────────────┐
60│ 2. Conversations (Conversation Orchestrator) groups communications into a │
61│ Conversation │
62│ - Normalizes channel events │
63│ - Groups related messages/utterances │
64│ - Tracks participants (CUSTOMER, HUMAN_AGENT, AI_AGENT) │
65└─────────────────────────────────────────────────────────────────────────────┘
66 │
67 ▼
68┌─────────────────────────────────────────────────────────────────────────────┐
69│ 3. Conversation events trigger Intelligence rules │
70│ - COMMUNICATION: on each new message/utterance │
71│ - CONVERSATION_END: when conversation closes │
72│ - CONVERSATION_INACTIVE: when conversation goes idle │
73└─────────────────────────────────────────────────────────────────────────────┘
74 │
75 ▼
76┌─────────────────────────────────────────────────────────────────────────────┐
77│ 4. Language Operators analyze the conversation │
78│ - Twilio-authored: Sentiment, Summary, NBR, Script Adherence │
79│ - Custom: domain-specific analysis with your prompts │
80│ - Context: enriched with Customer Memory + Enterprise Knowledge │
81└─────────────────────────────────────────────────────────────────────────────┘
82 │
83 ▼
84┌─────────────────────────────────────────────────────────────────────────────┐
85│ 5. Results delivered via webhook + REST API │
86│ - Real-time: Agent desktop, workflow triggers │
87│ - Post-call: CRM notes, QA systems, analytics │
88│ - Aggregated: Conversational Insights for cross-conversation analysis │
89└─────────────────────────────────────────────────────────────────────────────┘
90```
91
92**Key insight**: Real-time and post-conversation intelligence use the **same underlying model**. Start with low-friction post-call summaries, then progressively introduce real-time assist using the same components.
93
94## Scope
95
96### CAN
97
98- Analyze conversations in real-time (per-message) and post-conversation (at close/inactive) via Language Operators
99- Use 4 Twilio-authored operators: Sentiment, Summary, Next Best Response, Script Adherence
100- Create custom Language Operators with natural language prompts and structured output (TEXT, JSON, CLASSIFICATION) — `EXTRACTION` is a read-only format returned by some Twilio-authored operators; it cannot be set on custom operators you create
101- Define up to 5 rules per Intelligence Configuration, each with 1-5 operators (minimum 1 required), 0 or 1 trigger, and 0-2 webhook actions
102- Throttle real-time triggers with `count` parameter (run every N communications, min 1, max 20)
103- Deliver results via webhook (POST) and query historically via REST API
104- Track conversations across SMS, Voice, RCS, WhatsApp, Chat, and Email channels via Conversation Orchestrator (Conversations v2) integration
105- Create custom operators with parameters (`{{parameters.name}}` syntax), including knowledge base references (`KNOWLEDGE_BASE_AND_SOURCE_IDS` type — value format: `knowledge_base_id:knowledge_source_id`)
106- Enrich operators with Customer Memory (`context.memory.enabled: true`) and Enterprise Knowledge (`context.knowledge.bases: [...]`) at the rule or operator level
107- Add `trainingExamples` (input/output pairs) to custom operators to improve accuracy
108- Pin a specific operator version in a rule via `operators[].version`; omit to use latest
109- Query OperatorResults filtered by `intelligenceConfigurationId`, `conversationId`, or `operatorId`
110- Query operator versions and fetch specific version details
111- Delete Intelligence Configurations, custom Operators, and individual OperatorResults via REST API
112- Use ETag/If-Match headers for optimistic locking on **Operator** updates (returns 412 on mismatch) — ETag is not supported on Configuration updates
113- Filter Conversations by `status`, `channels`, `createdAtBefore`/`createdAtAfter`, `channelId`, `intelligenceConfigurationIds`, `operatorIds`
114- Authenticate with both Account SID/Auth Token and API Key/Secret
115- Define rules without a trigger (trigger is optional per spec; runs on all events if omitted)
116
117### CANNOT
118
119- **JSON-only API** — All v3 endpoints require `Content-Type: application/json`. Form-encoded bodies return HTTP 415 with error 20422.
120- **No standalone operation** — v3 requires Conversation Orchestrator (Conversations v2) for conversation capture. You cannot feed raw messages or recordings into v3 directly.
121- **No per-message sentiment** — Sentiment is conversation-level, accumulating across all messages. A conversation with one positive and one negative message returns "mixed", not separate results per message.
122- **No deleting Twilio-authored operators** — DELETE returns 404 "Operator not found" for Twilio-authored operators, not 403. They are not treated as "yours" to delete.
123- **No editing Twilio-authored operator prompts** — Twilio-authored operators have `prompt: null` when retrieved via GET. The prompt is hidden and not configurable.
124- **No more than 2 actions per rule** — The API enforces `size must be between 0 and 2` for actions.
125- **No more than 5 rules per configuration** — API enforces `size must be between 0 and 5`.
126- **No PCI or HIPAA compliance** — Conversation Intelligence v3 is not PCI compliant or HIPAA Eligible. Do not use for payment data or protected health information.
127- **No GET/PUT/DELETE on v3 Conversations** — The Conversations endpoint is read-only (GET list, GET by ID). Conversation lifecycle is controlled by Conversation Orchestrator, not by the Intelligence API.
128- **No unsupported JSON schema features** — The following are rejected in `outputSchema`: `minLength`/`maxLength` (strings), `patternProperties` (objects), `uniqueItems` (arrays). Use basic types only.
129- **Cannot use PUT to update a live configuration** — PUT creates an inactive version with no activation API. Operators silently stop returning results. Workaround: DELETE the configuration and POST to recreate it.
130- **Silent Memory Store linkage failures** — If `memoryStoreId` points to a deleted or invalid store, capture still works but identity resolution and extraction silently fail with no error. Implement periodic health checks to verify Memory Store linkage is functioning.
131
132## Quick Decision
133
134| Need | Use | Why |
135|------|-----|-----|
136| Real-time agent assist during live calls/chats | v3 + COMMUNICATION trigger + Next Best Response operator | Real-time webhook delivery per utterance |
137| Post-call QA scoring | v3 + CONVERSATION_END trigger + Script Adherence operator | Runs once at conversation close, returns detailed score |
138| Conversation sentiment tracking | v3 + Sentiment operator (Twilio-authored) | Conversation-level classification: positive/negative/neutral/mixed |
139| Post-call recording transcription + analysis | v2 Voice Intelligence (existing) | v3 does not ingest recordings directly — v2 pipeline handles recording→transcript→operators |
140| Custom domain-specific analysis | v3 + Custom operator with JSON output | Define prompt, parameters, structured output schema |
141| Cross-channel conversation history | Conversations v2 (Conversation Orchestrator) alone | v3 adds analysis on top; Conversation Orchestrator handles capture and history |
142| Simple keyword extraction | v3 + Custom operator (EXTRACTION format) | Structured extraction with custom prompt |
143
144## Decision Frameworks
145
146### v2 (Voice Intelligence) vs v3 (Conversation Intelligence)
147
148| Dimension | v2 (Voice Intelligence) | v3 (Conversation Intelligence) |
149|-----------|------------------------|-----------------------------------|
150| Input source | Recording SIDs (audio) | Conversation Orchestrator conversations (text/transcriptions) |
151| Channels | Voice only | SMS, Voice, RCS, WhatsApp, Chat, Email |
152| Operator management | Console only (attach to Intelligence Service) | REST API (full CRUD on custom operators) |
153| Trigger model | Post-transcription (async) | Real-time (per-message) or post-conversation |
154| Result delivery | Webhook (`voice_intelligence_transcript_available`) | Webhook (per-rule action) + REST query |
155| SDK support | `client.intelligence.v2.transcripts` | Twilio Node.js SDK supported |
156| SID prefix | `GA` (service), `GT` (transcript), `LY` (operator) | `intelligence_configuration_*`, `intelligence_operator_*` |
157| Status | GA | GA |
158| Coexistence | Works alongside v3 | Works alongside v2 |
159
160Use v2 when: You need post-call transcription from recordings, or need GA stability.
161Use v3 when: You need real-time analysis, cross-channel support, or API-managed custom operators.
162
163### Real-Time vs Post-Conversation
164
165| Factor | COMMUNICATION trigger | CONVERSATION_END trigger | CONVERSATION_INACTIVE trigger |
166|--------|----------------------|-------------------------|-------------------------------|
167| When it fires | On each new message/utterance | When conversation closes | When conversation goes idle |
168| Latency | Near real-time | Seconds after close | After inactive timeout |
169| Use cases | Agent assist, escalation detection, live compliance | QA scoring, summaries, CRM updates | Idle conversation follow-up |
170| Operator context | Accumulating — sees all messages so far | Complete conversation | Messages up to inactivity point |
171| Throttling | `count` parameter (every N messages) | N/A | N/A |
172| Cost implication | Runs per message (more executions) | Runs once per conversation | Runs once per inactivity event |
173
174### Operator Version Lifecycle
175
176| Status | Behavior | When |
177|--------|----------|------|
178| `PREVIEW` | Normal execution, restricted visibility | Internal/testing versions |
179| `ACTIVE` | Normal execution, full availability | Production-ready versions |
180| `DEPRECATED` | Executes with Warn event via Watch | Migration window — update to newer version |
181| `RETIRED` | Hard failure, Error logged in Watch | Must update Intelligence Configuration manually |
182
183### Custom Operator Output Formats
184
185| Format | Use When | Result Shape | Schema Support |
186|--------|----------|-------------|----------------|
187| TEXT | Free-form analysis, summaries, translations | `{"text": "..."}` | Auto-generated (not customizable) |
188| JSON | Structured extraction with custom fields | User-defined via `outputSchema` | Full JSON Schema (max 100 props, 10 nesting levels, 1000 enum values max) |
189| CLASSIFICATION | Category labeling (sentiment, intent, topic) | `{"label": "..."}` | Auto-generated |
190| EXTRACTION | Returned by some Twilio-authored operators only — cannot be set on custom operators | `{"entities": [{"text": "...", "label": "..."}]}` | N/A (read-only) |
191
192### Twilio-Authored Operator Reference
193
194Ready-to-use operators maintained by Twilio. Use these IDs directly in rules — no custom prompt required.
195
196| Operator | ID | Best Trigger | Use Case |
197|----------|-----|--------------|----------|
198| **Sentiment** | `intelligence_operator_01kcrvw16kfa88qvgrfmr7y151` | COMMUNICATION | Real-time sentiment tracking (positive/negative/neutral/mixed) |
199| **Summary** | `intelligence_operator_01kcv35pnkeysaf6z6cqtbpegn` | CONVERSATION_END | Post-call conversation summary |
200| **Next Best Response** | `intelligence_operator_01kea27sy7ffsafmtsfp17nzx4` | COMMUNICATION | Real-time agent assist with suggested responses |
201| **Script Adherence** | `intelligence_operator_01kf34tcyefpyb1t4m0nbd8rxg` | CONVERSATION_END | QA scoring for script compliance |
202
203**Note**: Twilio-authored operators have `author: "TWILIO"` and `prompt: null` when retrieved via GET. Prompts are hidden and not configurable. Use custom operators if you need control over the prompt.
204
205## Integration Patterns
206
207Code samples use raw `fetch()` for clarity, but the Twilio Node.js SDK is also supported for v3.
208
209### Authentication Helper
210
211```javascript
212const INTELLIGENCE_V3_BASE = 'https://intelligence.twilio.com/v3';
213
214function getAuthHeaders() {
215 const credentials = Buffer.from(
216 `${process.env.TWILIO_ACCOUNT_SID}:${process.env.TWILIO_AUTH_TOKEN}`
217 ).toString('base64');
218 return {
219 'Authorization': `Basic ${credentials}`,
220 'Content-Type': 'application/json',
221 };
222}
223```
224
225### Create Intelligence Configuration with Rules
226
227```javascript
228// Step 1: Create configuration (empty rules initially)
229const configResponse = await fetch(
230 `${INTELLIGENCE_V3_BASE}/ControlPlane/Configurations`,
231 {
232 method: 'POST',
233 headers: getAuthHeaders(),
234 body: JSON.stringify({
235 displayName: 'Customer Support Analytics',
236 description: 'Real-time sentiment + post-call summary',
237 rules: [],
238 }),
239 }
240);
241const config = await configResponse.json();
242// config.id = "intelligence_configuration_..."
243
244// Step 2: Add rules via PUT (replaces all rules)
245const updateResponse = await fetch(
246 `${INTELLIGENCE_V3_BASE}/ControlPlane/Configurations/${config.id}`,
247 {
248 method: 'PUT',
249 headers: getAuthHeaders(),
250 body: JSON.stringify({
251 displayName: 'Customer Support Analytics',
252 rules: [
253 {
254 operators: [
255 { id: 'intelligence_operator_01kcrvw16kfa88qvgrfmr7y151' }, // Sentiment
256 ],
257 triggers: [{ on: 'COMMUNICATION' }],
258 actions: [
259 { type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/realtime-results' },
260 ],
261 },
262 {
263 operators: [
264 { id: 'intelligence_operator_01kcv35pnkeysaf6z6cqtbpegn' }, // Summary
265 ],
266 triggers: [{ on: 'CONVERSATION_END' }],
267 actions: [
268 { type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/post-call-results' },
269 ],
270 },
271 ],
272 }),
273 }
274);
275const updatedConfig = await updateResponse.json();
276// updatedConfig.version = 2 (auto-incremented)
277```
278
279### Link to Conversation Orchestrator Conversation Configuration
280
281```javascript
282// Intelligence config must be linked to a Conversation Orchestrator conversation config
283// See conversation-orchestrator skill for full Conversation Orchestrator setup
284const convConfigResponse = await fetch(
285 'https://conversations.twilio.com/v2/ControlPlane/Configurations',
286 {
287 method: 'POST',
288 headers: getAuthHeaders(),
289 body: JSON.stringify({
290 displayName: 'Support Config',
291 memoryStoreId: 'mem_store_...', // Required — create via Memory API first
292 conversationGroupingType: 'GROUP_BY_PARTICIPANT_ADDRESSES',
293 intelligenceConfigurationIds: [config.id],
294 channelSettings: {
295 SMS: {
296 statusTimeouts: { inactive: 5, closed: 10 },
297 captureRules: [{ from: '*', to: '+1XXXXXXXXXX', metadata: {} }],
298 },
299 },
300 }),
301 }
302);
303```
304
305### Consume Operator Results
306
307```javascript
308// Query all results for an intelligence configuration
309const resultsResponse = await fetch(
310 `${INTELLIGENCE_V3_BASE}/OperatorResults?intelligenceConfigurationId=${config.id}`,
311 { headers: getAuthHeaders() }
312);
313const results = await resultsResponse.json();
314
315for (const operatorResult of results.items) {
316 console.log(`Operator: ${operatorResult.operator.id}`);
317 console.log(`Format: ${operatorResult.outputFormat}`);
318 console.log(`Payload: ${JSON.stringify(operatorResult.result)}`); // e.g. { text: "..." } or { label: "..." }
319 console.log(`Conversation: ${operatorResult.conversationId}`);
320 console.log(`Trigger: ${operatorResult.executionDetails.trigger.on}`);
321 // Context that was actually used at runtime (single source of truth):
322 console.log(`Memory profile: ${operatorResult.executionDetails.resolvedContext?.memory?.profileId}`);
323 console.log(`Knowledge sources: ${JSON.stringify(operatorResult.executionDetails.resolvedContext?.knowledge?.sources)}`);
324 // Cost/perf metadata:
325 console.log(`Model: ${operatorResult.metadata.system.resolvedModel}, latencyMs: ${operatorResult.metadata.system.latencyMs}`);
326}
327```
328
329### Paginate Through Results
330
331All list endpoints (`/OperatorResults`, `/Conversations`, `/Operators`, `/Configurations`) use cursor-based pagination. Default page size is 50; maximum is 1000.
332
333```javascript
334async function* getAllOperatorResults(configId) {
335 let pageToken = undefined;
336 do {
337 const url = new URL(`${INTELLIGENCE_V3_BASE}/OperatorResults`);
338 url.searchParams.set('intelligenceConfigurationId', configId);
339 url.searchParams.set('pageSize', '1000');
340 if (pageToken) url.searchParams.set('pageToken', pageToken);
341
342 const response = await fetch(url, { headers: getAuthHeaders() });
343 const data = await response.json();
344
345 yield* data.items;
346 pageToken = data.meta?.nextToken; // null/undefined when no more pages
347 } while (pageToken);
348}
349
350// Usage:
351for await (const result of getAllOperatorResults(config.id)) {
352 console.log(result.operator.id, result.result);
353}
354```
355
356### Enable Customer Memory and Enterprise Knowledge on a Rule
357
358```javascript
359// Context is configured at the rule level (not the operator level)
360rules: [
361 {
362 operators: [{ id: 'intelligence_operator_01kea27sy7ffsafmtsfp17nzx4' }], // NBR
363 triggers: [{ on: 'COMMUNICATION' }],
364 actions: [{ type: 'WEBHOOK', method: 'POST', url: 'https://your-app.com/nbr' }],
365 context: {
366 memory: { enabled: true }, // inject customer profile from Memory Store
367 knowledge: {
368 bases: ['knowledge_base_id_here'], // inject enterprise KB articles
369 },
370 },
371 },
372]
373```
374
375### Create a Custom Operator with Training Examples and Parameters
376
377```javascript
378const operatorResponse = await fetch(
379 `${INTELLIGENCE_V3_BASE}/ControlPlane/Operators`,
380 {
381 method: 'POST',
382 headers: getAuthHeaders(),
383 body: JSON.stringify({
384 displayName: 'Escalation Risk Detector',
385 prompt: `Analyze this conversation between a customer and agent.
386Product context: {{parameters.productName}}
387Classify escalation risk as LOW, MEDIUM, or HIGH based on customer frustration signals.`,
388 outputFormat: 'CLASSIFICATION',
389 parameters: {
390 productName: { type: 'STRING', required: true, description: 'Product line being discussed' },
391 // KNOWLEDGE_BASE_AND_SOURCE_IDS parameters are passed as "kb_id:source_id" at rule time
392 knowledgeContext: { type: 'KNOWLEDGE_BASE_AND_SOURCE_IDS', required: false },
393 },
394 trainingExamples: [
395 {
396 input: 'Customer: This is the third time I have called about this issue',
397 output: 'HIGH',
398 },
399 {
400 input: 'Customer: Thanks, I think that might work',
401 output: 'LOW',
402 },
403 ],
404 }),
405 }
406);
407const operator = await operatorResponse.json();
408// Pin this operator to a specific version in your rule:
409// operators: [{ id: operator.id, version: operator.version }]
410```
411
412## Gotchas
413
414### Setup
415
4161. **Memory Store is required for Conversation Orchestrator**: You cannot create a Conversations v2 Configuration without a `memoryStoreId`. The Memory API returns `"memoryStoreId: must not be null"` (error 20001). Create the Memory Store first via `POST memory.twilio.com/v1/ControlPlane/Stores`.
417
4182. **JSON-only API**: All v3 endpoints require `Content-Type: application/json`. Form-encoded bodies return HTTP 415 with error 20422 ("does not support this payload format"). This matches Conversation Orchestrator but differs from most Twilio APIs.
419
4203. **v2 and v3 coexist independently**: Creating v3 configurations does not affect v2 Intelligence Services (`GA*` SIDs). Both are accessible on the same account simultaneously. They share the `intelligence.twilio.com` host but use different URL paths (`/v2/Services` vs `/v3/ControlPlane/Configurations`).
421
422### Configuration
423
4244. **PUT creates an inactive version — operators stop returning results**: When you PUT to update an Intelligence Configuration, the new version is created in an **inactive state**. There is no activation API to make it live. Your operators will silently stop producing results. **Workaround: DELETE the configuration and POST to recreate it** with the updated rules/operators. This is the only reliable way to update a live configuration.
425
4265. **PUT replaces all rules**: Updating a configuration replaces the entire `rules` array. There is no PATCH or per-rule update. Always include all rules in the PUT body, not just the changed one.
427
4286. **Config version auto-increments**: Each PUT bumps the `version` field. Conversation Orchestrator conversation configs use `version` for optimistic locking, but Intelligence configs accept PUT without version checks.
429
4307. **Rules get their own IDs**: When rules are created via PUT, each gets an auto-generated ID (`intelligence_configurationrule_*`). These IDs appear in OperatorResult references but are not user-settable.
431
4328. **Trigger count parameter throttles execution**: Setting `{"on":"COMMUNICATION","parameters":{"count":3}}` runs the operator every 3 messages instead of every message.
433
434### Runtime
435
4369. **Dual capture rules cause duplicate processing**: If both inbound (`from: *, to: +1XXX`) and outbound (`from: +1XXX, to: *`) capture rules match the same SMS, Conversation Orchestrator creates two Communications for one message, and Intelligence produces two OperatorResults. Use unidirectional capture rules unless you specifically want both.
437
43810. **Twilio number is auto-typed HUMAN_AGENT**: In Conversation Orchestrator conversations, the Twilio number is automatically assigned `type: "HUMAN_AGENT"` and the external number gets `type: "CUSTOMER"` with automatic memory profile resolution (`mem_profile_*`).
439
44011. **Sentiment accumulates across messages**: The Sentiment operator analyzes the full conversation context, not individual messages. After a positive message, sentiment was "positive". After adding a negative message to the same conversation, it became "mixed".
441
44212. **Near real-time delivery for COMMUNICATION trigger**: Results are delivered via webhook shortly after each utterance — the full pipeline is Conversation Orchestrator capture → Intelligence trigger → Operator execution → webhook delivery.
443
44413. **Custom operator TEXT output auto-wraps**: Custom operators with `outputFormat: "TEXT"` always return `{"text": "..."}` regardless of the prompt. The outputSchema is auto-generated and not customizable for TEXT format.
445
446### Observability
447
44814. **conversationConfigurationId returns "unused"**: The v3 Conversations endpoint returns `"conversationConfigurationId": "unused"` instead of the actual Conversation Orchestrator config ID. Use `intelligenceConfigurationIds` array instead for linking.
449
45015. **No isTwilioAuthored field**: Twilio-authored operators are distinguished by `author: "TWILIO"`, custom operators by `author: "SELF"`. There is no boolean `isTwilioAuthored` field.
451
45216. **Twilio-authored operator prompts are hidden**: GET on a Twilio-authored operator returns `prompt: null`. You cannot inspect or modify the system prompt. Custom operators return the full prompt.
453
45417. **Two separate metadata sections**: OperatorResults carry both `metadata.system` and `executionDetails.resolvedContext` — they serve different purposes:
455 - `metadata.system`: cost and performance — `resolvedModel` (LLM used), `latencyMs`, `inputCharacters`/`outputCharacters` (billing units), `inputTruncated`
456 - `executionDetails.resolvedContext`: what context was actually injected at runtime — `memory` (`profileId`, `memoryStoreId`) and `knowledge` (`sources`: array of `{baseId, sourceId}`). This is the single source of truth for context resolution.
457
458### Error Handling
459
46018. **Error codes are consistent**: v3 uses Twilio standard error codes: `20001` (bad request/validation), `20404` (not found), `20422` (unsupported format), `70001` (operator validation). All include `userError: true` and descriptive messages.
461
46219. **Invalid operator ID gives specific error**: Using a non-existent operator ID in a rule returns 400 with code `70001` and message identifying the exact invalid operator.
463
464### JSON Schema
465
46620. **All JSON schema fields are required by default — and Twilio auto-sets this**: Twilio automatically sets `additionalProperties: false` and marks all provided fields as required in `outputSchema`. Do not add `required` or `additionalProperties` yourself — Twilio overwrites any values you provide. The practical consequence: if the LLM cannot populate a field, the operator execution fails. Use union types for nullable fields: `"type": ["string", "null"]`.
467
46821. **Nullable field pattern**: To make a field optional/nullable in JSON output, use array type union:
469```json
470{
471 "outputSchema": {
472 "type": "object",
473 "properties": {
474 "requiredField": { "type": "string" },
475 "optionalField": { "type": ["string", "null"] }
476 }
477 }
478}
479```
480This allows the operator to return `null` for fields where the LLM has insufficient context.
481
48221. **Unsupported JSON schema features cause silent or hard failures**: The following JSON Schema features are NOT supported in `outputSchema` and will be rejected. Stick to `type`, `enum`, `properties`, `items`, `anyOf`, `$defs`/`$ref`.
483 - Strings: `minLength`, `maxLength`
484 - Objects: `patternProperties`, `unevaluatedProperties`, `propertyNames`, `minProperties`, `maxProperties`
485 - Arrays: `unevaluatedItems`, `contains`, `minContains`, `maxContains`, `uniqueItems`
486
48722. **`executionDetails.context` was removed**: Older docs and some live responses may show `executionDetails.context`. This field was removed in a breaking change. Use `executionDetails.resolvedContext` — it contains `memory` (profileId, memoryStoreId) and `knowledge` (array of baseId/sourceId pairs).
488
48923. **Operator result query param is `intelligenceConfigurationId`** (not `intelligenceConfiguration`): The REST API filter parameter for listing OperatorResults by config is `intelligenceConfigurationId`. Using the shorter form returns unfiltered results.
490
49124. **`KNOWLEDGE_BASE_AND_SOURCE_IDS` parameter values must use colon-separated format**: When passing a knowledge base parameter to an operator at rule time, the value must be formatted as `"knowledge_base_id:knowledge_source_id"`. Passing just the KB ID or using any other separator returns a validation error. Only plaintext KB sources are supported.
492
49325. **`KNOWLEDGE_BASE_AND_SOURCE_IDS` parameters do not support `default`**: Unlike `STRING`, `INTEGER`, `NUMBER`, and `BOOLEAN` parameter types which all allow a `default` value, `KNOWLEDGE_BASE_AND_SOURCE_IDS` does not. Defining a `default` on a KB parameter will be ignored or rejected.
494
49526. **Each rule requires at least 1 operator**: The `operators` array on a rule has `minItems: 1`. Submitting a rule with an empty operators array on create or update returns a 400 validation error.
496
49727. **List endpoints are paginated — don't assume you got all results**: All list endpoints (`/OperatorResults`, `/Conversations`, `/Operators`, `/Configurations`) return a max of 50 items by default (max 1000 with `pageSize`). The response `meta.nextToken` is non-null when more pages exist. Always paginate when querying production data sets.
498
499## Conversational Insights (Cross-Conversation Analytics)
500
501Where the Intelligence API gives you per-conversation OperatorResults, the **Insights API v3** is the query layer for aggregating across thousands of conversations — grouping, filtering, and counting by dimensions like sentiment, channel, language, and operator output.
502
503Base URL: `https://insights.twilio.com`
504
505> **Public Beta** — Insights v3 is currently in public beta. The query schema is subject to change.
506
507### When to Use Insights vs Intelligence REST API
508
509| Goal | Use |
510|------|-----|
511| Get the result for a specific conversation | Intelligence API: `GET /v3/OperatorResults?conversationId=...` |
512| Count conversations by sentiment over time | Insights API: query with `OperatorResult.Value` dimension |
513| Find all conversations where agent went off-script | Insights API: filter on `OperatorResult.Value` |
514| Build a sentiment trend dashboard | Insights API: group by `DateCreated` + `OperatorResults` |
515| Discover available metrics and dimensions | Insights API: `GET /v3/InsightsDomains/Conversations/Metadata` |
516
517### Endpoints
518
519| Method | Path | Purpose |
520|--------|------|---------|
521| `POST` | `/v3/InsightsDomains/Conversations/Query` | Execute a semantic query, returns first page |
522| `GET` | `/v3/InsightsDomains/Conversations/Query?pageToken=...` | Fetch subsequent pages |
523| `GET` | `/v3/InsightsDomains/Conversations/Metadata` | Discover available cubes, measures, dimensions |
524
525Same Basic Auth as Intelligence API. JSON-only (`Content-Type: application/json`).
526
527### Query a Sentiment Distribution
528
529```javascript
530const INSIGHTS_BASE = 'https://insights.twilio.com';
531
532const response = await fetch(
533 `${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query`,
534 {
535 method: 'POST',
536 headers: getAuthHeaders(), // same helper as Intelligence API
537 body: JSON.stringify({
538 domain: 'Conversations',
539 query: {
540 measures: ['Conversation.Count'],
541 dimensions: ['OperatorResults', 'Channels', 'DateCreated'],
542 filters: [{
543 op: 'AND',
544 expressions: [
545 { op: 'IN', field: 'OperatorResult.Value', values: ['positive', 'negative'] },
546 ],
547 }],
548 orderBy: [{ field: 'OperatorResults.CreatedDate', direction: 'DESC' }],
549 },
550 }),
551 }
552);
553const data = await response.json();
554// data.items = [{ Id: 'conv1', OperatorResults: 'positive', Channels: ['voice'], ... }]
555// data.meta.nextToken — use for next page (null if last page)
556```
557
558**Query fields:**
559
560| Field | Description |
561|-------|-------------|
562| `query.measures` | What to aggregate — e.g. `"Conversation.Count"`, `"OperatorResult.Count"` |
563| `query.dimensions` | What to group by — e.g. `"OperatorResults"`, `"Channels"`, `"Languages"`, `"DateCreated"` |
564| `query.filters` | Nested filter tree with `op` + `expressions`. Filter ops: `AND`, `OR`, `EQ`, `NE`, `GT`, `LT`, `IN` |
565| `query.orderBy` | Sort by field + `ASC`/`DESC` |
566
567### Pagination
568
569POST returns the first page. Subsequent pages use GET with `pageToken`. Stop when `meta.nextToken` is null.
570
571```javascript
572async function* queryAll(queryBody) {
573 const first = await fetch(`${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query`,
574 { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(queryBody) }
575 ).then(r => r.json());
576 yield* first.items;
577
578 let nextToken = first.meta?.nextToken;
579 while (nextToken) {
580 const page = await fetch(
581 `${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Query?pageToken=${nextToken}`,
582 { headers: getAuthHeaders() }
583 ).then(r => r.json());
584 yield* page.items;
585 nextToken = page.meta?.nextToken;
586 }
587}
588```
589
590### Discover Available Dimensions and Measures
591
592```javascript
593const meta = await fetch(
594 `${INSIGHTS_BASE}/v3/InsightsDomains/Conversations/Metadata`,
595 { headers: getAuthHeaders() }
596).then(r => r.json());
597
598for (const cube of meta.cubes) {
599 console.log('Measures:', cube.measures.map(m => m.name));
600 console.log('Dimensions:', cube.dimensions.map(d => d.name));
601}
602```
603
604Known dimensions: `DateCreated`, `OperatorResults`, `OperatorResult.Value`, `Channels`, `Languages`, `Conversation.AccountSid`
605
606Known measures: `Conversation.Count`, `OperatorResult.Count`
607
608### Insights CANNOT
609- Return raw OperatorResult payloads — use Intelligence `GET /v3/OperatorResults` for that
610- Write anything — read-only
611- Query in real-time — data mart has indexing lag vs. the live Intelligence API
612- Query domains other than `Conversations`
613
614## Related Resources
615
616- [Conversation Orchestrator Skill](/.claude/skills/conversation-orchestrator/SKILL.md) — Conversation Orchestrator setup: Memory Store, Conversation Configuration, capture rules, participant types
617- [Twilio Conversations (unified stack)](/.claude/skills/conversations/SKILL.md) — End-to-end integration guide for Conversation Orchestrator + Conversation Memory + Intelligence v3 pipeline
618