Analytics API Reference
Authentication
All public endpoints require authentication via a product token. Requests must include the token in the X-CM-PRODUCTTOKEN header:
X-CM-PRODUCTTOKEN: <product-token>
You can find your token for the Analytics endpoints by opening your HALO studio, selecting Settings at the bottom left, and selecting Account. Click on Create Token, create a name for your token, and select the Analytics type. Store the token in a secured location.
Base URL
Swagger: https://api.cm.com/halo/analytics/docs
Breaking Changes
Removed endpoints:
-
GET /analytics/v1/accounts/{account_id}/agents
-
GET /analytics/v1/accounts/{account_id}/tools
-
GET /analytics/v1/accounts/{account_id}/profiles
Migration: Agent, tool, and profile data is now returned inline on each conversation in
GET /analytics/v2/accounts/{account_id}/conversations
in the fields:
-
visited_agents — array of { id, name } for each agent visited during the conversation
-
visited_tools — array of { id, name, description } for each tool visited during the conversation
-
profile_name — name of the profile the conversation belongs to
Envelope structure
The metrics endpoint no longer uses the envelope structure.
The conversations endpoint now returns the response body directly without meta, and pagination wrapper.
{
"data": [],
"total_count": 1523,
"page_index": 0,
"page_size": 1000,
"possible_language_filters": ["en", "nl", "de"],
"possible_product_filters": ["Support Chat", "Sales Bot"],
"possible_channel_filters": ["web", "whatsapp"]
}
Analytics v1 Endpoints
GET /analytics/v1/accounts/{account_id}/metrics
Retrieve aggregated conversation metrics for an account within a date range, optionally filtered by profile.
Path Parameters
|
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
account_id |
UUID |
Yes |
The account ID |
Query Parameters
|
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
from_date |
ISO 8601 datetime |
No |
30 days ago |
Start of the date range (UTC) |
|
until_date |
ISO 8601 datetime |
No |
Now |
End of the date range (UTC) |
|
profile_id |
integer |
No |
— |
Filter metrics to a specific profile. Returns 404 if not found |
Validation
-
from_date must be before until_date. Returns 422 Unprocessable Entity if violated.
-
If profile_id is provided, the profile must exist. Returns 404 Not Found otherwise.
Response (200 OK)
{
"total_conversations_count": 1520,
"total_average_conversation_duration_seconds": 145,
"total_average_interactions_per_conversation": 6.3,
"average_recognition_rate": 0.87,
"product_metrics": [
{
"product": "support-chat",
"conversations_count": 980,
"average_conversation_duration_seconds": 130,
"average_interactions_per_conversation": 5.8,
"average_recognition_rate": 0.91
}
]
}
Response Fields
|
Field |
Type |
Description |
|---|---|---|
|
total_conversations_count |
integer |
Total number of conversations in the date range |
|
total_average_conversation_duration_seconds |
integer |
Average conversation duration across all conversations (in seconds) |
|
total_average_interactions_per_conversation |
float |
Average number of interactions (messages) per conversation |
|
average_recognition_rate |
float or null |
Average recognition rate across conversations (0.0–1.0) |
|
product_metrics |
array or null |
Per-product breakdown (omitted if no products) |
Each entry in product_metrics:
|
Field |
Type |
Description |
|---|---|---|
|
product |
string |
Product name |
|
conversations_count |
integer |
Number of conversations for this product |
|
average_conversation_duration_seconds |
integer |
Average duration for this product (seconds) |
|
average_interactions_per_conversation |
float |
Average interactions per conversation |
|
average_recognition_rate |
float or null |
Average recognition rate for this product |
Error Responses
|
Status |
Description |
|---|---|
|
400 |
Invalid query parameters |
|
404 |
Account or profile not found |
|
422 |
from_date is not before until_date |
Analytics v2
GET /analytics/v2/accounts/{account_id}/conversations
List conversations for a profile with filtering, sorting, and pagination. Returns a paginated list of conversation overviews along with available filter options for the current date range.
Path Parameters
|
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
account_id |
UUID |
Yes |
The account ID |
Query Parameters
|
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
profile_id |
integer |
No |
— |
The profile to list conversations for |
|
duration_filter_in_days |
integer |
No |
— |
Number of days to look back from now. Overrides from_date/until_date if provided |
|
from_date |
ISO 8601 datetime |
No |
7 days ago |
Start of the date range (UTC) |
|
until_date |
ISO 8601 datetime |
No |
Now |
End of the date range (UTC) |
|
languages |
string (CSV) |
No |
— |
Filter by languages (e.g. en,nl) |
|
channels |
string (CSV) |
No |
— |
Filter by channels |
|
products |
string (CSV) |
No |
— |
Filter by products |
|
resolutions |
string (CSV) |
No |
— |
Filter by resolutions |
|
feedbacks |
string (CSV) |
No |
— |
Filter by feedback values |
|
search_query |
string |
No |
— |
Full-text search within conversation content |
|
min_recognition_rate_percentage |
float |
No |
— |
Minimum recognition rate filter (0–100) |
|
max_recognition_rate_percentage |
float |
No |
— |
Maximum recognition rate filter (0–100) |
|
agent_ids |
string (CSV) |
No |
— |
Filter by agent IDs (comma-separated integers) |
|
tool_ids |
string (CSV) |
No |
— |
Filter by tool IDs (comma-separated integers) |
|
topic_ids |
string (CSV) |
No |
— |
Filter by topic IDs (comma-separated integers, use -1 for pending) |
|
has_feedback |
boolean |
No |
— |
true = only with feedback, false = only without |
|
page_size |
integer |
No |
1000 |
Number of conversations per page (clamped to 1–1000) |
|
page_index |
integer |
No |
0 |
0-based page index |
|
sort_order |
string |
No |
desc |
Sort order: asc or desc |
|
sort_by |
string |
No |
started |
Sort field: started, ended, or recognition_rate |
Date Range Behavior
-
If duration_filter_in_days is provided, it takes precedence: from_date = now minus N days, until_date = now.
-
If neither duration_filter_in_days nor from_date/until_date are provided, defaults to the last 7 days.
-
If only from_date or until_date is provided, the other remains open-ended.
Pagination
This endpoint uses offset-based pagination via page_index and page_size query parameters. The response includes pagination metadata in nested meta and pagination objects:
|
Field |
Type |
Description |
|---|---|---|
|
meta.total_count |
integer |
Total number of conversations matching the filters |
|
pagination.page_size |
integer |
Requested page size |
|
pagination.has_next |
boolean |
Whether there is a next page |
|
pagination.has_previous |
boolean |
Whether there is a previous page |
|
pagination.next_cursor |
string or null |
Reserved for cursor-based pagination (currently null) |
|
pagination.previous_cursor |
string or null |
Reserved for cursor-based pagination (currently null) |
To iterate through pages, increment page_index and check pagination.has_next.
Response (200 OK)
{
"data": [],
"possible_language_filters": ["en", "nl", "de"],
"possible_product_filters": ["support-chat", "sales-chat"],
"possible_channel_filters": ["web", "whatsapp"],
"meta": {
"total_count": 342
},
"pagination": {
"page_size": 100,
"has_next": true,
"has_previous": false,
"next_cursor": null,
"previous_cursor": null
}
}
Response Fields
|
Field |
Type |
Description |
|---|---|---|
|
data |
array |
List of ConversationOverview objects (see below) |
|
possible_language_filters |
string array |
All distinct languages available in the current date range |
|
possible_product_filters |
string array |
All distinct products available in the current date range |
|
possible_channel_filters |
string array |
All distinct channels available in the current date range |
|
meta |
object |
Response metadata |
|
meta.total_count |
integer |
Total number of conversations matching the filters |
|
pagination |
object |
Pagination details |
|
pagination.page_size |
integer |
Requested page size |
|
pagination.has_next |
boolean |
Whether there is a next page |
|
pagination.has_previous |
boolean |
Whether there is a previous page |
|
pagination.next_cursor |
string or null |
Reserved for future cursor-based pagination |
|
pagination.previous_cursor |
string or null |
Reserved for future cursor-based pagination |
ConversationOverview Object
Each item in data represents a summary of a single conversation.
|
Field |
Type |
Description |
|---|---|---|
|
conversation_id |
UUID |
Unique conversation identifier |
|
profile_id |
integer or null |
Profile the conversation belongs to |
|
created |
datetime or null |
When the conversation record was created |
|
started |
datetime or null |
When the first message was exchanged |
|
ended |
datetime or null |
When the conversation ended |
|
first_user_message |
string or null |
The first message sent by the user |
|
most_common_language |
string or null |
Dominant language (2-character ISO code) |
|
most_common_origin |
string or null |
Most common origin/source |
|
most_common_channel |
string or null |
Most common channel (e.g. web, whatsapp) |
|
most_common_product |
string or null |
Most common product |
|
environment |
string or null |
Environment the conversation ran in (e.g. production, staging) |
|
matched_items |
integer |
Number of user messages that were successfully recognized |
|
total_items |
integer |
Total number of user messages |
|
recognition_rate |
float |
Ratio of matched to total items (0.0–1.0) |
|
visited_agent_ids |
integer array |
IDs of agents that handled the conversation |
|
visited_tool_ids |
integer array |
IDs of tools that were invoked during the conversation |
|
resolution |
string or null |
Resolution status (see Resolutions) |
|
resolution_reasoning |
string or null |
AI-generated reasoning for the resolution classification |
|
summary |
string or null |
AI-generated conversation summary |
|
feedback |
string |
User feedback rating (see Feedback) |
|
feedback_comment |
string or null |
Free-text comment left with feedback |
|
topic |
object or null |
Assigned topic (see Topics) |
|
subtopic |
object or null |
Assigned subtopic (see Subtopics) |
|
manual_resolution |
object or null |
Manual resolution override (see Manual Resolutions) |
|
external_handover |
object or null |
Handover metadata if conversation was transferred to a human agent |
|
messages |
array |
Simplified message list (see Messages List View) |
GET /analytics/v2/accounts/{account_id}/conversations/{conversation_id}
Retrieve a single conversation by ID with full calculated analytics, including detailed message history.
Path Parameters
|
Parameter |
Type |
Required |
Description |
|---|---|---|---|
|
account_id |
UUID |
Yes |
The account ID |
|
conversation_id |
UUID |
Yes |
The conversation ID |
Response (200 OK)
{
"profile_id": 1,
"profile_name": "My Profile",
"most_common_channel": "web",
"most_common_language": "en",
"most_common_origin": "website",
"most_common_product": "support-chat",
"started": "2025-01-15T10:30:00Z",
"ended": "2025-01-15T10:45:00Z",
"total_user_items": 8,
"num_matched_user_items": 7,
"recognition_rate": 0.875,
"messages": [],
"resolution": "RESOLUTION_SUCCESSFUL",
"resolution_reasoning": "The user's question was answered completely.",
"summary": "User asked about order status and received tracking info.",
"feedback": "FEEDBACK_SATISFIED",
"version": 2,
"visited_agent_ids": [1, 3],
"visited_agents": [
{ "id": 1, "name": "Order Agent" },
{ "id": 3, "name": "Tracking Agent" }
],
"visited_tool_ids": [2],
"visited_tools": [
{ "id": 2, "name": "Order Lookup", "description": "Looks up order status by order ID" }
],
"topic": {
"id": 5,
"name": "Order Status",
"definition": "Questions about order tracking and delivery"
},
"subtopic": {
"id": 12,
"name": "Tracking",
"definition": "Requests for tracking information"
},
"manual_resolution": null
}
Response Fields
|
Field |
Type |
Description |
|---|---|---|
|
most_common_channel |
string or null |
Dominant channel |
|
most_common_language |
string or null |
Dominant language |
|
most_common_origin |
string or null |
Dominant origin |
|
most_common_product |
string or null |
Dominant product |
|
started |
datetime |
Conversation start time |
|
ended |
datetime |
Conversation end time |
|
total_user_items |
integer |
Total number of user messages |
|
num_matched_user_items |
integer |
Number of recognized user messages |
|
recognition_rate |
float |
Recognition rate (0.0–1.0) |
|
messages |
array |
Full message history with detailed content (see Message Types Detail View) |
|
resolution |
string |
Resolution status |
|
resolution_reasoning |
string or null |
AI reasoning for resolution |
|
summary |
string or null |
AI-generated summary |
|
feedback |
string |
User feedback rating |
|
version |
integer |
Response format version (currently 2) |
|
visited_agent_ids |
integer array |
Agent IDs visited |
|
visited_tool_ids |
integer array |
Tool IDs invoked |
|
topic |
object or null |
Assigned topic |
|
subtopic |
object or null |
Assigned subtopic |
|
manual_resolution |
object or null |
Manual resolution override, if any |
Error Responses
|
Status |
Description |
|---|---|
|
404 |
Conversation not found, or conversation has no items/metadata |
Content Types
Messages in the conversation list endpoint (ConversationItemSummary) are a simplified view:
|
Field |
Type |
Description |
|---|---|---|
|
id |
integer |
Item ID |
|
profile_id |
integer or null |
Profile ID |
|
interaction_id |
UUID or null |
Interaction ID |
|
role |
string or null |
Message role (e.g. user, halo, assistant) |
|
name |
string or null |
Sender name |
|
text_content |
string or null |
Plain text content of the message |
|
match |
boolean or null |
Whether the message was recognized |
|
language |
string or null |
Detected language |
|
created |
datetime or null |
Timestamp |
|
image_carousel |
object or null |
Carousel content (only present if the message contains one) |
|
media |
array or null |
Media attachments (images, files) |
|
buttons |
array or null |
Conversation option buttons |
|
show_feedback |
string or null |
Feedback prompt text |
|
json_content |
array or null |
Structured JSON content |
Message Types (Detail View)
The single conversation detail endpoint returns full message objects. Each message is a tagged union with a type field:
|
Type |
Description |
|---|---|
|
USER |
Message from the end user |
|
HALO |
AI assistant response with full reasoning and metadata |
|
ASSISTANT |
Generic assistant message |
|
CUSTOMER_SERVICE_AGENT |
Message from a human customer service agent |
|
CUSTOMER_SERVICE_AGENT_INSTRUCTION |
Instruction message from a customer service agent |
|
EXTERNAL_HANDOVER |
Handover event with transfer details |
All message types share a common base containing content (array of content parts), timestamp, is_message_before_halo, and interaction_id.
Message Content Parts
The content array uses a type discriminator:
|
Type |
Fields |
Description |
|---|---|---|
|
text |
text |
Plain text content |
|
json |
json |
Arbitrary JSON content |
|
media |
mime_type, name?, data_uri? |
Media attachment |
|
buttons |
conversation_options |
Interactive buttons/options |
|
image_carousel |
image_carousel |
Image carousel widget |
|
show_feedback |
text |
Feedback prompt shown to user |
Agent & Tool Metadata
The following sections document the agent and tool metadata returned within the single conversation response. This metadata is nested inside HALO messages in the messages array via the agent_results field.
Agent Results
HALO messages contain an agent_results field listing the agents invoked during that conversation turn. Each entry is an AgentResult object:
|
Field |
Type |
Description |
|---|---|---|
|
agent_id |
integer or null |
Numeric agent ID (matches entries in top-level visited_agent_ids) |
|
agent_name |
string or null |
Display name of the agent |
|
agent_version_id |
integer or null |
Version ID of the agent configuration used |
|
handover_reasoning |
string or null |
AI-generated reasoning for why the conversation was routed to this agent |
|
tool_uses |
array |
Tools that the agent requested to invoke (see Tool Uses) |
|
tool_results |
array |
Results from tool executions (see Tool Results) |
Tool Uses
Each ToolUse in the tool_uses array represents a tool invocation request made by the agent. It is a tagged union with a type field indicating the tool category.
Tool Use Types:
|
Value |
Description |
|---|---|
|
function |
A built-in function tool |
|
flow_tool |
A flow-based tool (visual workflow) |
|
mcp_tool |
An MCP (Model Context Protocol) server tool |
|
routing |
An agent routing decision |
Common fields (all variants):
|
Field |
Type |
Description |
|---|---|---|
|
type |
string |
Tool type discriminator (function, flow_tool, mcp_tool, routing) |
|
id |
string |
Unique identifier for this tool use instance |
|
name |
string |
Name of the tool |
|
arguments |
array |
List of arguments passed to the tool |
The flow_tool includes an additional field:
|
Field |
Type |
Description |
|---|---|---|
|
tool_id |
integer |
The configured tool's numeric ID (matches entries in top-level visited_tool_ids) |
Tool Use Arguments:
|
Field |
Type |
Description |
|---|---|---|
|
parameter |
string |
Parameter name |
|
value |
any |
Parameter value (JSON) |
Tool Results
Each ToolResult in the tool_results array represents the outcome of a tool execution.
|
Field |
Type |
Description |
|---|---|---|
|
tool_name |
string |
Name of the tool that was executed |
|
tool_id |
integer or null |
Numeric tool ID (matches entries in top-level visited_tool_ids) |
|
tool_version_id |
integer or null |
Version ID of the tool configuration used |
|
tool_use_id |
string |
Identifier linking this result to its corresponding tool use entry |
|
state |
string or null |
Execution flow state |
|
execution_id |
string or null |
Unique execution identifier for tracing |
|
trace |
array or null |
Execution trace entries |
|
output |
array or null |
Tool output as content parts (text, JSON, media, etc.) |
|
started |
datetime |
When tool execution started (ISO 8601) |
|
finished |
datetime |
When tool execution completed (ISO 8601) |
Flow State:
|
Value |
Description |
|---|---|
|
running |
Tool execution is currently in progress |
|
parked |
Execution is paused, waiting for external input |
|
completed |
Execution finished successfully |
|
terminated |
Execution was terminated due to an error |
Trace:
Each trace entry records a step in the tool's execution flow.
|
Field |
Type |
Description |
|---|---|---|
|
id |
integer |
Trace step identifier |
|
started |
datetime |
When the step started (ISO 8601) |
|
finished |
datetime or null |
When the step completed (ISO 8601), null if ongoing |
|
variables |
object or null |
Key-value map of variables at this execution step |
Topics
Topics represent the subject matter of a conversation, classified automatically by AI analysis. They form the parent level of a two-level taxonomy representing broad subject areas (e.g. "Billing", "Technical Support", "Account Management").
|
Field |
Type |
Description |
|---|---|---|
|
id |
integer |
Topic ID |
|
name |
string |
Human-readable topic name |
|
definition |
string |
Description of what the topic covers |
Use the topic_ids query parameter in the conversations list endpoint to filter by topic. A value of -1 matches conversations where topic classification is still pending.
Subtopics
Subtopics are child categories within a topic, providing more granular classification. They represent specific subjects within a broader topic area (e.g. under "Billing": "Invoice Dispute", "Payment Method", "Refund Request").
Every classified conversation has a subtopic; the parent topic is derived from the subtopic's position in the taxonomy.
|
Field |
Type |
Description |
|---|---|---|
|
id |
integer |
Subtopic ID |
|
name |
string |
Human-readable subtopic name |
|
definition |
string |
Description of what the subtopic covers |
Resolutions
Resolution indicates the outcome of a conversation. It is determined using the following priority order:
-
External handover — if the conversation was handed over to a human agent, it is automatically classified as RESOLUTION_HANDOVER.
-
Manual resolution — if an analyst has manually overridden the resolution, the manual label takes precedence.
-
AI resolution — if neither of the above apply, the AI-determined resolution is used.
-
Pending — if none of the above are available, the resolution defaults to RESOLUTION_PENDING.
|
Value |
Description |
|---|---|
|
RESOLUTION_SUCCESSFUL |
The user's question or request was resolved successfully |
|
RESOLUTION_HANDOVER |
The conversation was transferred to a human agent |
|
RESOLUTION_UNSUCCESSFUL |
The conversation ended without the user's issue being resolved |
|
RESOLUTION_UNDETERMINED |
The AI could not confidently determine the outcome |
|
RESOLUTION_PENDING |
Resolution has not yet been classified (e.g. conversation is still ongoing or classification is pending) |
The resolution_reasoning field (string or null) contains the AI's explanation of why it assigned a particular resolution label. This is only present when the resolution was determined by AI.
Manual Resolutions
A manual resolution is an override applied by an analyst or reviewer to correct or adjust the AI-assigned resolution. When a manual resolution is active, it takes precedence over the AI resolution in the priority chain.
|
Field |
Type |
Description |
|---|---|---|
|
id |
integer |
Manual resolution record ID |
|
profile_id |
integer or null |
Profile ID the resolution was applied to |
|
conversation_id |
UUID |
Conversation this resolution applies to |
|
user_name |
string or null |
Name of the user who applied the override |
|
original_label |
string or null |
The resolution label before the override (null if no prior label existed) |
|
new_label |
string |
The new resolution label applied by the reviewer |
|
reasoning |
string or null |
Free-text explanation of why the resolution was changed |
|
is_active |
boolean |
Whether this manual resolution is currently active |
|
created |
datetime |
When the manual resolution was created |
|
user_email |
string |
Email of the user who applied the override |
original_label can be any value from the Resolutions section (or null if no prior label existed).
new_label is restricted to:
-
RESOLUTION_SUCCESSFUL
-
RESOLUTION_UNSUCCESSFUL
Feedback
Feedback represents the end-user's satisfaction rating for a conversation, mapped from a 1–5 numeric score.
|
Value |
Numeric Score |
Description |
|---|---|---|
|
FEEDBACK_VERY_SATISFIED |
5 |
Very satisfied |
|
FEEDBACK_SATISFIED |
4 |
Satisfied |
|
FEEDBACK_NEUTRAL |
3 |
Neutral |
|
FEEDBACK_UNSATISFIED |
2 |
Unsatisfied |
|
FEEDBACK_VERY_UNSATISFIED |
1 |
Very unsatisfied |
|
FEEDBACK_NOT_GIVEN |
— |
No feedback provided |