Skip to main content

Dashboard

Aggregates over the active project's calls.

Summary​

GET /api/dashboard?window=7d

ParamDefaultValues
windowtodaytoday, 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.

StatusCause
400{"detail": "invalid window, expected one of today|yesterday|7d|14d|30d"}
headline_metrics and performance_score belong to reports

Reports 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.