Skip to content
SIP.IO
DocsStart free

Queue Metrics & CDR

Every queue call writes one row to a durable CDR timing layer in the PresenceEngine, and the standard contact-center KPIs (ASA, service level, abandon rate, and AHT) are derived from those four timestamps. As with agent KPIs, there’s no separate aggregation job: the metrics are a pure read over the CDR within a time window.

CREATE TABLE queue_cdr(
call_id TEXT PRIMARY KEY,
queue_id TEXT,
agent_id TEXT, -- who answered (NULL until answered)
enqueued_at INTEGER, -- entered the queue (epoch ms)
connected_at INTEGER, -- answered by an agent (NULL until answered)
ended_at INTEGER, -- talk ended (NULL until the call ends)
disposition TEXT -- offered | answered | abandoned | timeout
);

One row per offered call, written and updated across the call’s life by the same single-threaded brain that runs the ACD fence:

Lifecycle eventEffect on the row
EnqueueInsert with enqueued_at and disposition = offered.
Answer (agent confirms)Set connected_at, agent_id, disposition = answered.
Abandon / timeout (before answer)Set disposition = abandoned or timeout and ended_at.
Hang-up (after answer)Set ended_at (the talk-end).

The three timestamps enqueued_at → connected_at → ended_at are all you need to derive every KPI below.

All metrics report over [since, now] and share a service-level threshold parameter, slSec (default 20 seconds).

offered = rows for the queue in the window
answered = rows where disposition = answered
abandoned = rows where disposition ∈ { abandoned, timeout }
abandonRatePct = round( 100 × abandoned / offered ) // 0 if offered = 0

The mean wait before an agent answered, over answered calls:

wait = connected_at − enqueued_at
asaMs = round( mean(wait over answered calls) ) // 0 if none answered

The share of all offered calls answered within slSec:

slMet = answered calls where (connected_at − enqueued_at) ≤ slSec×1000
serviceLevelPct = round( 100 × slMet / offered ) // 0 if offered = 0

Note the denominator is offered, not answered: an abandoned call counts against service level, which is the stricter (and standard contact-center) definition.

The mean talk time over calls that have ended:

ahtMs = round( mean(ended_at − connected_at over answered calls with ended_at) )

AHT here is talk time. It does not yet fold in the agent’s post-call wrap_up; that’s tracked separately in agent state-time and is a planned addition to AHT.

Queue KPIs are read from the public API at GET /v1/queues (the queues scope):

{
"queues": [
{
"queue": "q_support",
"offered": 120,
"answered": 108,
"abandoned": 12,
"abandonRatePct": 10,
"asaMs": 8200,
"serviceLevelPct": 84,
"ahtMs": 254000
}
]
}

Historical rollups: GET /v1/reports/queues and GET /v1/metrics/queues

Section titled “Historical rollups: GET /v1/reports/queues and GET /v1/metrics/queues”

The KPIs above are computed live, as a pure read over queue_cdr for whatever window you ask for. A separate per-account MetricsDO drains queue_cdr (and agent_event) on a 5-minute cron and folds it into durable rollup tables at 15-minute (q15) and daily (q1d) grain, dimensioned by queue. This is additive: the live endpoint and KPIs above are unchanged, and the rollups give you pre-aggregated history beyond the live window without re-walking raw CDR rows.

Rollup rows store numerator/denominator pairs (e.g. total wait time and answered count, rather than a pre-computed ASA), divided once at read time — this avoids a mean-of-means error from averaging already-averaged buckets.

Read the rollups from the public API at GET /v1/reports/queues (the reports scope). GET /v1/metrics/queues is an alias over the same rollup data:

Terminal window
curl 'https://api.sip.io/v1/reports/queues?since=1719500000000&grain=15m' -H 'x-api-key: sk_…'
{
"queues": [
{
"queue": "q_support",
"grain": "15m",
"bucket_start": 1719504900000,
"offered": 42,
"answered": 38,
"abandoned": 4,
"asaMs": 7900,
"serviceLevelPct": 86,
"ahtMs": 249000
}
]
}

Team × queue rollups: GET /v1/reports/teams

Section titled “Team × queue rollups: GET /v1/reports/teams”

Agents can be organized into teams for reporting. GET /v1/reports/teams returns the same queue-timing rollups sliced by team, so you can compare how each team performs against the same queues rather than only per-agent or account-wide:

Terminal window
curl 'https://api.sip.io/v1/reports/teams?since=1719500000000' -H 'x-api-key: sk_…'

Team CRUD (GET/POST /v1/agent-teams, GET/POST /v1/agent-teams/{id}) lives on the Agent KPIs page.

Alongside the WebSocket-pushed wallboard, GET /v1/live/queues returns a point-in-time REST snapshot of queue health (waiting count, longest wait, SLA-at-risk, alerts) for consumers that want a poll instead of a socket — see Live Supervisor Wallboard for the full set.

Once tiered out of the live rollup window, MetricsDO’s q15/q1d (and the agent-side aq15/as15/ap15) tables are archived as NDJSON to the same object-storage data lake used for call CDRs — 15-minute rollups after roughly 30 days, daily rollups after roughly 13 months. This is a separate archive path from the call-level CDR archive: it holds aggregated queue/agent metrics, not per-call records.

  • Configurable service-level target per queue (today slSec defaults to 20s at the report layer).
  • Wrap-up in AHT: folding post-call work into handle time.