# Analytics

What the calls added up to: how many, how long, how they ended, what they cost, and what callers thought. Two endpoints — one for totals over a window, one for the same numbers as a series you can plot.

## The overview

`GET /v1/analytics/overview`

**Shell**

```bash
curl -s "https://voice.sphoro.com/v1/analytics/overview?from=2026-09-01&to=2026-09-08" \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

**Response200 OK**

```json
{
  "from": "2026-09-01",
  "to": "2026-09-08",
  "calls": {
    "total": 4182,
    "inbound": 1204,
    "outbound": 2978,
    "connected": 3106,
    "average_duration_seconds": 84
  },
  "end_reasons": {
    "ended_by_api": 1902,
    "no_answer": 604,
    "voicemail": 288,
    "busy": 104,
    "at_capacity": 41,
    "bad_number": 63
  },
  "cost": { "total": 18240.55, "currency": "INR" },
  "csat": { "responses": 311, "average": 4.3 }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `from` optional | date | Start of the window, inclusive. |
| `to` optional | date | End of the window, inclusive. |
| `agent_id` optional | string | Restrict to one agent. The slice to use when comparing two prompts. |
| `project_id` optional | string | Restrict to one [cost centre](https://voice.sphoro.com/docs/projects). |

## As a series

`GET /v1/analytics/timeseries`

**Shell**

```bash
curl -s "https://voice.sphoro.com/v1/analytics/timeseries?from=2026-08-01&to=2026-09-08&interval=day&metric=calls" \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

**Response200 OK**

```json
{
  "metric": "calls",
  "interval": "day",
  "points": [
    { "at": "2026-08-01", "value": 412 },
    { "at": "2026-08-02", "value": 388 }
  ]
}
```

The same filters apply. This is the shape for a dashboard, and for the question analytics actually answers best: *has it changed*, rather than *what is it*.

## The numbers worth watching

| Metric | What a change in it means |
| --- | --- |
| **Connected rate** | Falling on outbound is usually the list, not the agent — check `bad_number` and `not_connected` separately, because they are different problems. |
| **`at_capacity`** | Anything above zero is calls you never placed. Rising over a week is a ceiling you have grown into. See [concurrency](https://voice.sphoro.com/docs/capacity). |
| **Average duration** | A sudden drop usually means callers hanging up early, which is a prompt or a greeting problem. A rise means the agent is not closing. |
| **`voicemail`** | High on a campaign is a timing problem — you are calling when nobody is there. |
| **Cost per connected call** | The number that catches a chain quietly falling through to a more expensive vendor. See [vendors](https://voice.sphoro.com/docs/providers). |

> **Split by `end_reason` before drawing any conclusion about volume.** "Calls are down" and "connected calls are down" are different sentences with different causes, and the totals hide which one you have.

## Recording what callers thought

CSAT is not collected automatically — you record it, from wherever you actually ask. A follow-up SMS, a form on your site, a rating in your app.

`POST /v1/analytics/csat`

**Shell**

```bash
curl -s -X POST https://voice.sphoro.com/v1/analytics/csat \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"call_id": "'"$CALL_ID"'", "score": 4, "comment": "Sorted it quickly."}'
```

`csat.received` fires when one lands, so it can drive your own alerting. Scores are joined to the call, which means they slice by agent and project like everything else — which is the point: "which prompt do people prefer" is answerable.

## Per-call detail

Analytics is aggregate. For one call, the record itself carries its duration, end reason, timings, cost and the vendors it used:

`GET /v1/calls`

**Shell**

```bash
curl -s "https://voice.sphoro.com/v1/calls?agent_id=$AGENT_ID&limit=100" \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

For latency specifically, `connect_ms` and `carrier_handoff_ms` are the two to read first — see [latency](https://voice.sphoro.com/docs/latency).
