Skip to content
SIP.IO
DocsStart free

Agent KPIs

Agent KPIs are computed by the per-account PresenceEngine directly from its agent-state event log (agent_event). Because every state transition is already recorded durably, the metrics are a pure function of that log over a time window, so there’s no separate metrics pipeline to keep in sync.

All KPIs take a since timestamp (epoch ms) and report over the window [since, now].

For each agent, the brain walks agent_event in order and sums the time spent in each bucket. The duration of a transition into bucket S at ts[i] runs until the next transition ts[i+1], or until now for the most recent one:

statusMs = { ready: …, incall: …, wrapup: …, paused: …, idle: …, … } // ms per bucket
loggedInMs = Σ statusMs excluding `offline`

statusMs is the raw building block; loggedInMs is total time logged in (everything except offline).

Occupancy answers “while available, how much of the time was the agent actually working a call?” It’s a measure of how hard staffed agents are being worked.

handle = incall + wrapup
occupancyPct = round( 100 × handle / (handle + ready) ) // 0 if denominator is 0

The denominator is handle + ready: productive time plus time sitting idle-but-available. Time paused, offline, or unreachable is excluded entirely, so occupancy isolates “of the time you were on the floor ready to work, what fraction was spent on calls.”

Utilization answers “of the agent’s whole logged-in shift, how much was spent handling calls?” It’s handle time against everything except being logged out.

utilizationPct = round( 100 × handle / loggedInMs ) // loggedInMs excludes offline

Because the denominator includes paused and idle time, utilization ≤ occupancy for the same agent over the same window. Occupancy measures intensity while ready; utilization measures share of the whole shift.

Agent KPIs are read from the public API at GET /v1/agents (the agents scope):

Terminal window
curl 'https://api.sip.io/v1/agents?since=1719500000000' -H 'x-api-key: sk_…'
{
"agents": [
{
"user_id": "1001",
"statusMs": { "ready": 5400000, "incall": 2700000, "wrapup": 300000, "paused": 600000 },
"loggedInMs": 9000000,
"occupancyPct": 35,
"utilizationPct": 33,
"adherencePct": 92
}
]
}

adherencePct is included when the agent has a work schedule; see that page for its meaning and the null / "bypass" cases.

Historical rollups: GET /v1/reports/agents

Section titled “Historical rollups: GET /v1/reports/agents”

The KPIs above are computed live, directly from PresenceEngine’s event log, over whatever window you ask for. A separate per-account MetricsDO drains PresenceEngine’s agent_event table on a 5-minute cron and folds it into its own durable rollup tables, at 15-minute (aq15, as15) and daily (aq1d, as1d) grain. This is additive: it doesn’t replace the live endpoint above, it gives you pre-aggregated history beyond the live window without re-walking the raw event log.

Rollup rows store numerator/denominator pairs rather than pre-divided averages, and the division happens once at read time. This avoids a mean-of-means error you’d otherwise get by averaging pre-computed percentages across buckets.

Read the rollups from the public API at GET /v1/reports/agents (the reports scope):

Terminal window
curl 'https://api.sip.io/v1/reports/agents?since=1719500000000&grain=15m' -H 'x-api-key: sk_…'

Occupancy and utilization — a different formula

Section titled “Occupancy and utilization — a different formula”

GET /v1/reports/agents computes occupancy and utilization differently from the live GET /v1/agents endpoint above. Both are legitimate; they answer slightly different questions, so don’t expect the two numbers to match:

FormulaEndpoint
Live occupancyhandle / (handle + ready)GET /v1/agents
Rollup occupancyhandle / (login − paused)GET /v1/reports/agents
Rollup utilizationhandle / loginGET /v1/reports/agents

Where login = handle + ready + paused + idle + ring + extcall (total on-clock time). The rollup occupancy formula divides handle time by on-clock time excluding pauses, rather than by handle+ready — so it isolates “of the time the agent was actually staffed and not on a break, how much was spent on calls.” Rollup utilization is close to, but not necessarily identical to, the live utilization figure above, since it’s computed from the rollup’s login definition rather than loggedInMs.

Teams: agent-level rollups by team × queue

Section titled “Teams: agent-level rollups by team × queue”

Agents can be grouped into teams for reporting purposes. A team is a named set of agents; rollups can then be sliced by team × queue in addition to plain agent × queue.

Manage teams via GET/POST /v1/agent-teams and GET/POST /v1/agent-teams/{id} (the reports scope):

Terminal window
curl -X POST 'https://api.sip.io/v1/agent-teams' -H 'x-api-key: sk_…' \
-d '{ "name": "Tier 2 Support", "agent_ids": ["1001", "1002"] }'

Team-rolled-up reports are read from GET /v1/reports/teams; see Queue Metrics & CDR for the queue-side shape of that report.

When an agent pauses, they can supply a free-text reason string (not a fixed enum). If omitted, it’s bucketed under "unspecified". MetricsDO tracks pause time per reason at 15-minute and daily grain (ap15, ap1d), and GET /v1/reports/pauses returns the breakdown:

{
"pauses": [
{ "reason": "lunch", "count": 14, "totalMs": 25200000 },
{ "reason": "training", "count": 3, "totalMs": 5400000 },
{ "reason": "unspecified", "count": 2, "totalMs": 900000 }
]
}

This is the finer per-segment breakdown of paused time that was previously only a single bucket in state-time above.

  • Raw-event export: periodic flush of agent_event to the data lake for ad-hoc and historical analysis beyond the live window (superseded for KPI purposes by the MetricsDO rollups above, though the raw log itself isn’t yet separately exported).