Skip to content
MeridFlow AiFlow v8.x • self-hosted

Analytics

Aggregate volume, sentiment, and funnel trends across one agent or every agent, and across every channel, a phone call, a widget chat, an inbound email, or a silent background task. This is what backs the admin dashboard's Analytics page. Every call is also classified individually (see "Sentiment and outcome" below); this endpoint aggregates that per-call data rather than drawing from a separate data source.

Endpoint

GET /api/v1/analytics/overview

Authentication and roles

Takes an admin session token (see Authentication). Any role can call it, including Viewer, the same read-only observability bar as the Calls log itself.

Query parameters

Parameter Type Description
agent_id integer Scope to one agent. Omit for every agent combined.
channel string Scope to one channel: phone, web, email, or agent_task. Omit for every channel combined. Only scopes total_calls/completed_calls/abandoned_calls/avg_duration_seconds/sentiment/funnel/volume_by_day/top_agents, never volume_by_channel itself (see below).
start_date date (YYYY-MM-DD) Inclusive lower bound on Call.created_at.
end_date date (YYYY-MM-DD) Inclusive upper bound on Call.created_at.
curl "https://api.your-domain.com/api/v1/analytics/overview?agent_id=1&start_date=2026-01-01" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/analytics/overview",
    params={"agent_id": 1, "start_date": "2026-01-01"},
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
overview = response.json()
{
  "total_calls": 128,
  "completed_calls": 96,
  "abandoned_calls": 22,
  "avg_duration_seconds": 143.5,
  "sentiment": { "positive": 61, "neutral": 30, "negative": 12, "unclassified": 25 },
  "funnel": { "queued": 128, "ringing": 4, "answered": 106, "completed": 96 },
  "volume_by_day": [
    { "date": "2026-01-01", "count": 12 },
    { "date": "2026-01-02", "count": 18 }
  ],
  "top_agents": [
    { "agent_id": 1, "agent_name": "Support Bot", "call_count": 128 }
  ],
  "volume_by_channel": [
    { "channel": "phone", "count": 90 },
    { "channel": "web", "count": 30 },
    { "channel": "email", "count": 6 },
    { "channel": "agent_task", "count": 2 }
  ]
}

Fields

Field Type Description
total_calls integer Every call matching the filters (despite the name, this counts every channel, not just phone).
completed_calls integer status == completed.
abandoned_calls integer status in no_answer, failed, or busy.
avg_duration_seconds number or null Average of duration_seconds across calls that have one; null if none do.
sentiment object Counts by classified sentiment, plus unclassified for calls with no sentiment set yet.
funnel object queued (pending), ringing, answered (in_progress/completed/transferred), completed. Always scoped to the phone channel alone, regardless of the channel filter, the same way volume_by_channel always ignores it: queued/ringing are phone-only realities, a chat or email session never rings.
volume_by_day array One entry per day with at least one call, ascending by date.
top_agents array Up to 10 agents by call count, descending. Trivially one entry when agent_id is set.
volume_by_channel array Every channel always present, phone/web/email/agent_task, zero-filled rather than omitted. Reflects agent_id/start_date/end_date but never the channel filter itself, so a filtered view still reads in context of the whole.

Sentiment and outcome

Every call is classified individually by a one-shot Gemini pass over its transcript at finalize time, following the same "never block on failure" pattern that warm transfer and every other call-finalize side effect follows. This produces sentiment (positive, neutral, or negative) and a short free-text outcome_tag (for example "resolved" or "booked appointment"), both returned on the Call resource itself. A call too short to have a transcript worth judging simply stays unclassified.

Next

  • Calls: the per-call record sentiment and outcome_tag live on.
  • Live architecture: what is happening right now, rather than how a date range went.