Overview
Dashboards let you create custom visualizations of your call data. Each dashboard contains widgets — individual charts that plot a specific field using filters, chart types, and aggregation functions.
Key concepts:
- Dashboard — a container that holds widgets and defines shared filters applied to all widgets within it.
- Widget — a single chart that plots one field (e.g. call duration, success rate, metric scores).
- Filters — JSON-based query conditions applied at the dashboard level, widget level, or both.
Using dashboards in the app
Dashboards live under Dashboards in the sidebar. The rest of this guide
covers the underlying configuration and API; this section walks through the UI.
Creating a dashboard
The first time you open Dashboards you’ll see an empty state. Click Create
dashboard, give it a name, and you’ll land on the new (empty) dashboard ready
for widgets.
Dashboard controls
Every dashboard has a control bar:
- Date range — pick the window all widgets plot, up to the last 30 days
(see Time range).
- Agent filter — scope every widget to one or more agents, or leave it on
All agents.
- Add widget, Refresh, and an Edit toggle.
- A ⋮ menu to rename, duplicate, or delete the dashboard, or include it in
the daily report email.
Click Add widget to open the widget editor:
- Name the widget.
- Choose what to plot — a call data field (success, duration,
call_ended_reason, a custom metadata key, …) or a metric.
- Pick a chart type (line, bar, pie, list, or stat).
- For time-series charts, choose a time period (hour/day/week/month) and an
aggregation (count, sum, avg, min, max, …).
- Optionally group by a dimension and add widget-level filters.
- Save. The widget appears on the grid.
Toggle Edit to rearrange the grid: drag widgets to reorder them, edit,
duplicate, or remove them, then Save. Undo reverts unsaved changes.
Exploring data
Hover any chart for exact values. Click a data point to open a side sheet
of the call logs behind it, filtered to that bucket and group — a fast way to go
from a spike on the chart to the individual calls that caused it.
For any widget that plots a metric, you can set up an
alert on that metric without
leaving the dashboard. Open the widget’s ⋮ menu and choose Create alert on
this metric — the New alert drawer opens with the widget’s metric already
selected, so you just pick the alert type and Slack delivery and save.
This option only appears on metric-based widgets (those plotting
metric_evaluations.value with a metric selected). Widgets built on call-data
fields like success or duration don’t show it.
Time range
A dashboard’s date range controls the window every widget plots, and it is
capped at 30 days:
- You can select any window from the last 5 minutes up to the last 30 days,
using the quick ranges (Last 1 hour, Last 24 hours, Last 7 days, Last 30 days,
…) or a custom from/to range.
- A custom range cannot span more than 30 days, and its start cannot be
earlier than 30 days ago. Picking a range outside these bounds shows a
validation error (“Date range cannot exceed 30 days” / “Start date must be
within the last 30 days”).
The 30-day cap applies to the dashboard view. To analyze a longer history,
query the call logs directly via the API, where you can set arbitrary
timestamp filters.
This range is part of the dashboard’s saved filters — it persists
with the dashboard and is applied to every widget. The per-widget time period
(hour/day/week/month) chosen in the widget editor is separate: it only controls
the bucket size used to aggregate data within the selected range.
Filters
Filters are used on both dashboards and widgets to scope the data being visualized. Dashboard-level filters apply to all widgets, and widget-level filters apply only to that widget. When both are present, they are combined with AND logic.
Filter Structure
A filter is a JSON object. It can be a single condition or a group of conditions combined with a logical operator.
Single condition:
{
"field": "success",
"op": "eq",
"value": true
}
Grouped conditions:
{
"operator": "and",
"conditions": [
{ "field": "success", "op": "eq", "value": true },
{ "field": "duration", "op": "gte", "value": 60 }
]
}
Groups can be nested to build complex queries:
{
"operator": "and",
"conditions": [
{ "field": "success", "op": "eq", "value": true },
{
"operator": "or",
"conditions": [
{ "field": "duration", "op": "gte", "value": 120 },
{ "field": "call_ended_reason", "op": "contains", "value": "timeout" }
]
}
]
}
Logical Operators
| Operator | Description |
|---|
and | All conditions must match |
or | At least one condition must match |
same_row | All conditions must match on the same related row (see Metric Evaluation Filters) |
Comparison Operators
| Operator | Description |
|---|
eq | Equals |
neq | Not equals |
gt | Greater than |
gte | Greater than or equal to |
lt | Less than |
lte | Less than or equal to |
contains | Case-insensitive substring match |
startswith | Case-insensitive prefix match |
endswith | Case-insensitive suffix match |
in | Value is in the provided list |
isnull | Field is null |
regex | Regex pattern match |
Supported Filter Fields
Call Log Fields
| Field | Type | Supported Operators |
|---|
id | integer | eq, in, gt, gte, lt, lte |
success | boolean | eq, neq, isnull |
duration | number | eq, gt, gte, lt, lte |
customer_number | string | eq, neq, contains, startswith, in |
call_ended_reason | string | eq, neq, contains, in |
dropoff_point | string | eq, neq, contains, in, isnull |
topic | string | eq, neq, contains, in, isnull |
timestamp | datetime | eq, gt, gte, lt, lte |
is_reviewed | boolean | eq |
| Field | Type | Description |
|---|
agent.id | integer | Agent ID |
agent.agent_name | string | Agent name |
You can filter on top-level metadata keys using dot notation:
{ "field": "metadata.org_name", "op": "eq", "value": "Acme Corp" }
{ "field": "metadata.region", "op": "in", "value": ["US", "EU"] }
Only top-level metadata keys are supported. Nested keys like metadata.address.city are not allowed.
Metric Evaluation Fields
| Field | Type | Description |
|---|
metric_evaluations.value | dynamic | Evaluation value |
metric_evaluations.metric.id | integer | Metric ID |
metric_evaluations.metric.name | string | Metric name |
When filtering on multiple metric evaluation fields (e.g. metric name AND value), use the same_row operator to ensure conditions apply to the same evaluation row:{
"operator": "same_row",
"conditions": [
{ "field": "metric_evaluations.metric.name", "op": "eq", "value": "accuracy" },
{ "field": "metric_evaluations.value", "op": "gte", "value": 0.9 }
]
}
Without same_row, an and operator could match a call where one evaluation has the right metric name and a different evaluation has the right value.
Relative Datetime Values
Datetime fields support relative values for dynamic time-based filtering:
| Value | Description |
|---|
now | Current UTC time |
now-1d | 1 day ago |
now-7d | 7 days ago |
now-2h | 2 hours ago |
now-30m | 30 minutes ago |
today | Start of today (00:00:00 UTC) for gte/gt, end of today (23:59:59 UTC) for lte/lt |
today-7d | 7 days before today |
Supported time units: s (seconds), m (minutes), h (hours), d (days), w (weeks), M (months), y (years).
Example — calls from the last 7 days:
{
"operator": "and",
"conditions": [
{ "field": "timestamp", "op": "gte", "value": "today-7d" },
{ "field": "timestamp", "op": "lte", "value": "now" }
]
}
A widget plots a single field as a chart. You configure it with a field, chart type, and optional aggregation and time period.
Supported Fields
| Field | Data Type | Description |
|---|
duration | numeric | Call duration in seconds |
success | boolean | Whether the call was successful |
is_reviewed | boolean | Whether the call has been reviewed |
call_ended_reason | string | Reason the call ended |
dropoff_point | string | Point at which the caller dropped off |
topic | string | Call topic |
agent_id | numeric | Agent identifier |
metric_evaluations.value | dynamic | Metric evaluation value (requires selecting a metric) |
metadata.* | string | Any top-level metadata key (e.g. metadata.org_name) |
Chart Types
| Chart Type | Description | Best For |
|---|
line | Individual data points plotted over time | Viewing trends for numeric or datetime fields |
bar | Data aggregated into time buckets | Comparing aggregated values across time periods |
pie | Distribution of categorical values | Showing proportions for boolean or string fields |
Aggregation Functions
Used with bar charts to aggregate values within each time bucket.
| Function | Description | Applicable Data Types |
|---|
count | Count of records | All types |
sum | Sum of values | Numeric |
avg | Average of values | Numeric, Boolean |
min | Minimum value | Numeric, Datetime |
max | Maximum value | Numeric, Datetime |
Time Periods
Used with bar charts to define the time bucket size.
| Period | Description |
|---|
hour | Group by hour |
day | Group by day |
week | Group by week |
month | Group by month |
Valid Combinations
Not all field + chart type + aggregation combinations are valid. The rules depend on the field’s data type:
| Data Type | Allowed Chart Types | Allowed Aggregations |
|---|
| Numeric | line, bar | count, sum, avg, min, max |
| Boolean | pie, bar | count, avg |
| String | pie, bar | count |
| Datetime | line, bar | count, min, max |
The metric_evaluations.value field is dynamic — its data type and allowed chart types depend on the selected metric’s evaluation type. For example, an ENUM metric only supports pie charts, while a NUMERIC metric supports line, bar, and pie.
Line chart — individual data points:
[
{ "id": 1, "timestamp": "2025-11-03T00:00:00Z", "value": 45 },
{ "id": 2, "timestamp": "2025-11-03T01:00:00Z", "value": 62 }
]
Bar chart — aggregated time buckets:
[
{ "time_interval": "2025-11-03T00:00:00Z", "value": 4.5, "sample_count": 10 },
{ "time_interval": "2025-11-04T00:00:00Z", "value": 3.2, "sample_count": 8 }
]
Pie chart — value distribution (e.g. call_ended_reason with pie chart):
[
{ "label": "customer_ended_call", "value": 320, "percentage": 45.7 },
{ "label": "agent_ended_call", "value": 210, "percentage": 30.0 },
{ "label": "timeout", "value": 105, "percentage": 15.0 },
{ "label": "error", "value": 65, "percentage": 9.3 }
]
Use the Get Widget Data endpoint to fetch the plot data for a saved widget. You can optionally override the widget’s filters by passing a filters query parameter as a JSON string.
Use Preview Widget Data to test a widget configuration before saving it — pass the full widget definition in the request body along with optional dashboard_filters to preview what the chart would look like.
How Filters Are Applied
When fetching widget data, filters are merged in this order:
- Dashboard filters — applied to all widgets in the dashboard.
- Widget filters — applied to this specific widget only.
- Override filters (optional) — passed at request time via the
filters parameter.
If both dashboard and widget filters are present, they are combined using AND logic:
{
"operator": "and",
"conditions": [
{ /* dashboard filters */ },
{ /* widget filters */ }
]
}
You can visualize any top-level metadata key as a widget field using the metadata.* prefix. For example, to chart the distribution of a custom region field you set on your calls:
- Field:
metadata.region
- Chart type:
pie
Metadata fields are treated as strings, so they support pie and bar chart types with count aggregation.
Only top-level metadata keys are supported for widget fields. Nested metadata keys are not supported.
API Reference