Dashboard
Aggregates over the active project's calls.
Summary
GET /api/dashboard?window=7d
| Param | Default | Values |
|---|---|---|
window | today | today, yesterday, 7d, 14d, 30d |
The reports endpoint uses a different set of windows — see its own page before driving both from a single picker.
{
"window": "7d",
"range": { "from": "2026-09-14T00:00:00Z", "to": "2026-09-21T00:00:00Z" },
"stats": {
"calls_total": 891,
"calls_inbound": 142,
"calls_outbound": 690,
"calls_uploaded": 59,
"picked_up": 543,
"pickup_rate": 0.61,
"avg_duration_seconds": 97.4,
"outcome_breakdown": {
"resolved": 201,
"follow_up_scheduled": 168,
"no_interest": 96,
"voicemail": 78
},
"sentiment_breakdown": { "positive": 312, "neutral": 189, "negative": 42 }
}
}
stats carries these nine keys. Both breakdowns omit values with no calls
rather than reporting them as zero — an empty window returns {} for each, not
a full set of zeroes.
| Status | Cause |
|---|---|
400 | {"detail": "invalid window, expected one of today|yesterday|7d|14d|30d"} |
headline_metrics and performance_score belong to reportsReports reuse this same stats object and add two more
keys to it: headline_metrics, a funnel of counts, and performance_score.
This endpoint never sets either one, so they are always absent here. The
written summary — headline, wins, issues, recommended action — is a separate
narrative block, also returned only by reports.
headline_metrics is a funnel of counts, not prose. The written summary —
headline, wins, issues, recommended action — is a separate narrative block
returned by reports, not by this endpoint.
Trend
GET /api/dashboard/trend?days=30
{
"trend": [
{ "date": "2026-09-19", "calls": 118 },
{ "date": "2026-09-20", "calls": 142 },
{ "date": "2026-09-21", "calls": 96 }
]
}
Call volume per day. Takes days rather than window.
Leaderboard
GET /api/dashboard/leaderboard?window=30d
Per-agent performance, split by who was on the call:
{
"ai_agents": [
{
"agent_id": 3,
"agent_name": "Aria",
"calls_total": 412,
"picked_up": 259,
"pickup_rate": 0.63,
"avg_duration_seconds": 104.2,
"positive_sentiment_rate": 0.58
}
],
"human_agents": [
{
"agent_uuid": "us1a2b3c-…",
"agent_name": "Arjun Rao",
"calls_total": 188,
"picked_up": 121,
"pickup_rate": 0.64,
"avg_duration_seconds": 142.8
}
],
"unassigned": [
{
"agent_name": "Unassigned",
"calls_total": 291,
"picked_up": 163,
"pickup_rate": 0.56,
"avg_duration_seconds": 88.1
}
]
}
The three groups are deliberately separate rather than ranked together — an AI agent's pickup rate and a person's are not comparable, and averaging them produces a number that means nothing.
unassigned covers calls with no agent attributed, usually bulk uploads with
no metadata. A large bucket here means the leaderboard is measuring less than
it appears to; fix it by setting the agent on upload or with
PATCH /api/calls/{id}.
positive_sentiment_rate is null where too few calls have sentiment to be
meaningful.
Segment filtering
The dashboard endpoints accept segment_uuids like the call list does, so the
same aggregates can be narrowed to one campaign or region:
GET /api/dashboard?window=30d&segment_uuids=sg1a2b3c-…
Windows
Windows are resolved server-side against the server's clock, in whole days
starting at midnight. 7d means the last seven complete days plus today, and
range in the response tells you exactly what was covered — worth displaying
rather than assuming.