Define and use custom fields
Custom fields let you extend the Conversation Insights data model with the signals that matter to your business, and then query them the same way you query built-in data.
Conversation Insights aggregates the numeric values your custom language operators produce. Register a numeric field from a custom operator as a custom measure, and its value becomes queryable across your entire conversation volume: average lead scores by channel, compare QA ratings between AI and human agents, or track a compliance score week over week, with no separate pipeline to build.
You register every custom field under a category, which determines how you can use it in a query. Measures, the numeric values you aggregate, are the first supported category, and the rest of this page covers registering one. Support for custom dimensions, the attributes you filter and group by, is planned. For the difference between measures and dimensions, see Core concepts.
Registration maps a name you choose to a source field in a custom operator's output. Conversation Insights validates that the source field resolves against the operator's latest active version, then makes the field available in both cubes:
ConversationSummary.<Name><Aggregation>on the gold cube, at one row per conversationOperatorResult.<Name><Aggregation>on the silver cube, at operator result grain
<Aggregation> is one of Sum, Avg, Min, or Max, so a measure registered as LeadScore is queryable as ConversationSummary.LeadScoreAvg, OperatorResult.LeadScoreMax, and so on. Conversations where the operator didn't run return null for that field.
Registration applies going forward only. A custom measure captures values from operator runs that happen after you register it, and existing conversations aren't backfilled, so plan to register a measure before the period you want to report on.
Registering a custom measure is permanent
Your account has three custom measure slots. Registering a measure binds a name to a specific operator, and that binding can't be removed or reassigned to a different operator. Attempting to reuse a name for a different operator returns a 409 response.
Register measures you intend to keep. Iterating on an operator costs nothing, so confirm it produces the value you expect before you register it. See Verify the operator before you register.
A custom measure can be any numeric value your operator produces. Common choices include the following.
| Signal | What it captures | Example output field |
|---|---|---|
| Lead score | How qualified a prospect is | leadScore |
| QA score | Conversation quality against your own rubric | agentScore |
| Compliance score | Share of required disclosures actually delivered | disclosureScore |
| Escalation risk | Likelihood the conversation needs a human | escalationRisk |
| Upsell propensity | Likelihood a customer accepts an offer | upsellPropensity |
For more ideas about the signals a custom operator can extract, see Custom operator examples, which covers scenarios such as conversation scoring, lead qualification, escalation risk detection, and compliance detection.
Measures must be numeric
You can register only numeric fields as measures. If the signal you want is currently a string, such as a score returned as "87", change that field's type to number in your operator's outputSchema before you register it. Conversation Insights can't aggregate string fields, including classification labels.
Custom fields read from custom operator output, so the operator must already exist and be running against your conversations before you register a field.
Creating and running operators is the job of Conversation Intelligence and Conversation Orchestrator. Conversation Insights only aggregates what those products produce, so complete all three of the following steps outside Conversation Insights first.
- In Conversation Intelligence, create a custom operator with a JSON output format. Custom measures require a numeric field in the operator's output, so set
outputFormattoJSONand include the numeric field in youroutputSchema. See Create custom operators and Structuring JSON schema. - In Conversation Intelligence, attach the operator to an intelligence configuration. Operators run through configurations. See Create an intelligence configuration and Define rules.
- In Conversation Orchestrator, attach that intelligence configuration to your Orchestrator configuration, so the operator runs against your conversations. See Conversation Orchestrator.
Once the operator is producing results, you can register one of its numeric output fields in Conversation Insights.
You need two values to register a measure: the operator's identifier and the name of the numeric field in its output.
For example, a lead scoring operator might use the following output schema:
1{2"type": "object",3"properties": {4"leadScore": { "type": "number" },5"reasoning": { "type": "string" }6}7}
Here the field to register is leadScore. The reasoning field is a string, so it can't be a measure.
Conversation Insights validates the field against the operator's latest active version. If you change the operator's output schema and remove the field, later registrations for that field fail.
Because a registration permanently consumes one of your three slots, confirm the operator is producing the value you expect before you register it. Do this in Conversation Intelligence rather than in Conversation Insights:
- Run the operator against a few representative conversations.
- Inspect the operator results and confirm the field appears with a numeric value, not a string or a null.
- Confirm the field name in the results matches the name in your
outputSchemaexactly, including capitalization.
Iterating on an operator costs nothing. Only registration consumes a slot.
Before registering, check how many of your three slots are still available. Send a GET request to the Capacity subresource:
1curl -X GET "https://insights.twilio.com/v3/ControlPlane/ConversationInsights/CustomFieldMappings/Capacity" \2-u "$TWILIO_API_KEY:$TWILIO_API_SECRET"
1{2"measure": {3"consumed": 1,4"remaining": 25}6}
Send a POST request to the CustomFieldMappings resource with the name you want to use, an optional description, the operator identifier, and the field:
1curl -X POST "https://insights.twilio.com/v3/ControlPlane/ConversationInsights/CustomFieldMappings" \2-u "$TWILIO_API_KEY:$TWILIO_API_SECRET" \3-H "Content-Type: application/json" \4-d '{5"category": "MEASURE",6"name": "LeadScore",7"description": "Lead quality score from the lead scoring operator",8"entityMetadata": {9"sourceType": "INTELLIGENCE_OPERATOR",10"operatorId": "intelligence_operator_01k6fc25s7epm9qtk8rszbv3q5",11"field": "leadScore"12}13}'
The name you choose becomes the base of the measure names you query, so pick something you want to see in your reports and dashboards. Names can contain only letters, numbers, and underscores.
The description is optional free text of up to 150 characters. Use it to record what the measure represents, so others on your team can identify it when they list your registered measures. The description is for display only and isn't part of the uniqueness check, so you can change it by sending the registration again with a new description.
A successful registration returns 201:
1{2"id": "convinsights_mapping_01m31967b7em08vh1pqgbam3mj",3"category": "MEASURE",4"name": "LeadScore",5"description": "Lead quality score from the lead scoring operator",6"entityMetadata": {7"sourceType": "INTELLIGENCE_OPERATOR",8"operatorId": "intelligence_operator_01k6fc25s7epm9qtk8rszbv3q5",9"operatorName": "Lead Scoring",10"field": "leadScore"11},12"createdAt": "2026-08-13T14:30:00Z",13"updatedAt": null14}
The measure is queryable as soon as operator runs start producing values for it. Because registration isn't retroactive, expect empty results until the operator runs again on new conversations.
Registration is idempotent for the same name and operator. Sending the same request again returns 200 with the existing mapping rather than consuming another slot. Sending the same name with a different operatorId returns 409, because a name belongs permanently to the operator it was first registered against.
| Response | What it means | What to do |
|---|---|---|
400 | The field doesn't resolve against the operator's latest active version, or isn't numeric | Confirm the field name matches the operator's outputSchema exactly and that its type is number |
403 | The credentials don't have permission to register custom fields | Use an API key with access to the account that owns the operator |
403 | MAPPING_CAPACITY_REACHED: all three custom measure slots are in use | Check your remaining slots. Registrations are permanent, so a used slot can't be freed |
409 | The name is already registered to a different operator | Choose a different name. A name belongs permanently to the operator it was first registered against |
To list every custom measure registered for your account, send a GET request to the CustomFieldMappings resource:
1curl -X GET "https://insights.twilio.com/v3/ControlPlane/ConversationInsights/CustomFieldMappings" \2-u "$TWILIO_API_KEY:$TWILIO_API_SECRET"
1{2"mappings": [3{4"id": "convinsights_mapping_01m31967b7em08vh1pqgbam3mj",5"category": "MEASURE",6"name": "LeadScore",7"description": "Lead quality score from the lead scoring operator",8"entityMetadata": {9"sourceType": "INTELLIGENCE_OPERATOR",10"operatorId": "intelligence_operator_01k6fc25s7epm9qtk8rszbv3q5",11"operatorName": "Lead Scoring",12"field": "leadScore"13},14"createdAt": "2026-08-13T14:30:00Z",15"updatedAt": null16}17]18}
Your registered measures also appear in the Metadata resource response, alongside the built-in measures and dimensions.
An operator can produce more than one result for the same conversation, such as a real-time operator that re-evaluates as the conversation progresses. The two cubes handle that differently, and the difference shows up in your numbers.
- On the gold
ConversationSummarycube, the most recent result wins. The conversation carries a single value for the measure, reflecting the state of the conversation at close rather than a history of runs. - On the silver
OperatorResultcube, each result keeps its own row, so aggregations span every run.
Consider a QA operator that runs three times during one conversation, returning 70, then 80, then 90:
| Measure | Rows for that conversation | Result for that conversation |
|---|---|---|
ConversationSummary.AgentScoreAvg | One row: 90 | 90 |
OperatorResult.AgentScoreAvg | Three rows: 70, 80, and 90 | 80 |
The summary row is a snapshot written when the conversation closes, so a result that arrives after that point doesn't appear on the gold cube, and updating the row afterward is planned. If your operator runs after the conversation ends, query the measure on the silver OperatorResult cube, which records every result regardless of when it lands. See How the summary row is built.
An average taken across your conversations therefore uses only each conversation's final score on the gold cube, and every individual score on the silver cube. Neither is wrong: use the gold cube to ask about conversations, where each conversation counts once no matter how many times an operator ran, and the silver cube to ask about the operator results themselves.
Because you register one specific field, there is no ambiguity about which value in the operator's output becomes the measure.
Once registered, a custom measure behaves like any other measure. Choose an aggregation by adding its suffix to the measure name: Sum, Avg, Min, or Max. For example, ConversationSummary.LeadScoreAvg returns the average lead score. To count conversations, use ConversationSummary.Count.
This example averages lead scores by channel, using the gold ConversationSummary cube so that each conversation contributes once:
1{2"domain": "Conversations",3"query": {4"measures": ["ConversationSummary.LeadScoreAvg"],5"dimensions": ["ConversationSummary.Channels"],6"filters": [7{8"expressions": [9{10"op": "GT",11"field": "ConversationSummary.CreatedDate",12"values": ["2026-04-02"]13}14]15}16]17}18}
Custom measures pay off most on the gold cube, because your measure sits on the same row as everything else known about that conversation. You can slice a score your own operator produced by attributes that come from a different product entirely.
This example averages an AgentScore measure by both channel and whether an AI agent took part. The score comes from your custom operator in Conversation Intelligence, while the channel and the HasAIAgent flag come from Conversation Orchestrator:
1{2"domain": "Conversations",3"query": {4"measures": ["ConversationSummary.AgentScoreAvg"],5"dimensions": [6"ConversationSummary.Channels",7"ConversationSummary.HasAIAgent"8],9"filters": [10{11"expressions": [12{13"op": "GT",14"field": "ConversationSummary.CreatedDate",15"values": ["2026-04-02"]16}17]18}19]20}21}
The response returns one row per combination, so you can read your QA score across both axes at once:
1{2"domain": "Conversations",3"items": [4{5"ConversationSummary.Channels": "voice",6"ConversationSummary.HasAIAgent": false,7"ConversationSummary.AgentScoreAvg": "87.4"8},9{10"ConversationSummary.Channels": "voice",11"ConversationSummary.HasAIAgent": true,12"ConversationSummary.AgentScoreAvg": "79.1"13},14{15"ConversationSummary.Channels": "sms",16"ConversationSummary.HasAIAgent": true,17"ConversationSummary.AgentScoreAvg": "91.6"18}19],20"meta": {21"key": "items",22"nextToken": null,23"pageSize": 50,24"previousToken": null25}26}
Answering the same question from the silver cubes would take one query for the operator results, another for participant data, and a join in your own application on ConversationId.
To analyze the individual operator results rather than the conversation-level rollup, query the same measure on the silver OperatorResult cube as OperatorResult.LeadScoreAvg.
For the difference between the two cubes and guidance on which to use, see Core concepts.
- Your account has three custom measure slots.
- A registration is permanent. A name can't be deleted or reassigned to a different operator.
- You can register only numeric fields as measures. String fields, including classification labels, aren't eligible.
- Only custom operators with an
outputFormatofJSONproduce fields eligible for registration. - Registration isn't retroactive. Only operator runs after registration contribute values, and existing conversations aren't backfilled.
- Conversations where the operator didn't run return
nullfor the measure.
Explore the following resources to learn more:
- Core concepts: Understand cubes, measures, and dimensions.
- Aggregate conversation data: Run queries against the Metadata and Query resources.
- Query syntax: Build queries with filters, aggregations, and time granularity.