> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Insights API

> AI-agent cost, ROI, containment, and benchmarking dashboards — including the LLM-spend tracker. All reads served from a read-replica.

# Insights API

Operator-facing analytics for your AI agents: how much they cost to run (LLM spend), the revenue they attribute and the margin they return (ROI), how often they resolve a conversation without a human (containment), and how each agent ranks against the rest of your org (comparison & benchmarks).

These are read dashboards. They're served from a database read-replica, so they're cheap to call but lag the primary by 1-3 seconds. The two `PUT` endpoints (budget and ROI config) write to the primary and are read-back consistent.

**Base path:** `/api/v1/insights`

**Authentication:** API key (`X-API-Key`) or session JWT. Every request is scoped to the caller's organization; you only ever see your own tenant's data.

## Conventions

These apply to every endpoint below.

* **Money is always integer cents.** Fields ending in `_cents` are whole cents in the org's wallet currency (returned as `currency`, e.g. `USD`). The API never converts to dollars or applies FX — format on the client.
* **Time ranges use `from` and `to`** as ISO 8601 timestamps with an offset (e.g. `2026-06-01T00:00:00Z`). `from` must be earlier than `to`, and the window may not exceed 365 days. Omit both to default to the last 30 days ending now; pass only one and the other is backfilled to a 30-day window.
* **Timezone-aware buckets.** Day/month boundaries are computed in the org's billing timezone (returned as `timezone`), not UTC, so a cap or trend lines up with your local day.
* **Envelope.** Responses are wrapped as `{ "data": { … }, "meta": { "request_id": "…", "timestamp": "…" } }`. The shapes below show the `data` payload.
* **Invalid query parameters return `422`** with a `details.issues` array naming the offending `field` and a `message`.

## LLM spend

Track how much your AI agents are spending on model calls, where it's going, and whether you're on track against a budget.

| Method | Path                                           | Purpose                                                        |
| ------ | ---------------------------------------------- | -------------------------------------------------------------- |
| `GET`  | `/api/v1/insights/llm-spend/overview`          | Header stat cards: today, month-to-date, projection, cap usage |
| `GET`  | `/api/v1/insights/llm-spend/summary`           | Spend grouped by agent, model, or conversation                 |
| `GET`  | `/api/v1/insights/llm-spend/timeseries`        | Spend over time (hourly or daily buckets)                      |
| `GET`  | `/api/v1/insights/llm-spend/top-conversations` | The most expensive conversations in a window                   |
| `GET`  | `/api/v1/insights/llm-spend/by-feature`        | Spend broken down by AI feature/channel + threshold breaches   |
| `GET`  | `/api/v1/insights/llm-spend/budget`            | Current AI budget config                                       |
| `PUT`  | `/api/v1/insights/llm-spend/budget`            | Update the AI budget config                                    |

### Overview

`GET /api/v1/insights/llm-spend/overview` takes no parameters. It returns today's spend, month-to-date spend with a month-end projection and a vs-previous-period delta, the trailing 7-day spend, and how much of the configured daily cap is used. `cap_percentage`, `projected_month_end_cents`, and `vs_last_period_percent` are `null` when there's no cap configured or no historic spend to project from.

```json theme={null}
{
  "currency": "USD",
  "timezone": "America/New_York",
  "daily_cap_cents": 50000,
  "today": {
    "spend_cents": 12340,
    "cap_percentage": 24.7
  },
  "month_to_date": {
    "spend_cents": 184200,
    "projected_month_end_cents": 421000,
    "vs_last_period_percent": 12.4,
    "days_remaining_in_month": 13
  },
  "last_7d_spend_cents": 78900
}
```

### Summary

`GET /api/v1/insights/llm-spend/summary`

| Param     | Type                                 | Default      | Notes                  |
| --------- | ------------------------------------ | ------------ | ---------------------- |
| `from`    | ISO 8601                             | last 30 days | Window start           |
| `to`      | ISO 8601                             | now          | Window end             |
| `groupBy` | `agent` \| `model` \| `conversation` | `agent`      | How to group the spend |
| `limit`   | integer                              | `50`         | Max group rows (1-200) |

Returns `totals` for the whole window plus a `groups` array, each row carrying `group_id`, `group_label`, `model`, `total_tokens`, `total_cost_cents`, and `conversation_count`, ordered by cost descending.

### Timeseries

`GET /api/v1/insights/llm-spend/timeseries`

| Param         | Type            | Default                            | Notes        |
| ------------- | --------------- | ---------------------------------- | ------------ |
| `from`        | ISO 8601        | last 30 days (day) / 7 days (hour) | Window start |
| `to`          | ISO 8601        | now                                | Window end   |
| `granularity` | `hour` \| `day` | `day`                              | Bucket size  |

Returns a `series` array of `{ bucket, total_tokens, total_cost_cents }` ordered ascending. Hour granularity is capped at a 14-day window — use `granularity=day` for longer ranges, or you'll get a `422`.

### Top conversations

`GET /api/v1/insights/llm-spend/top-conversations`

| Param   | Type     | Default      | Notes           |
| ------- | -------- | ------------ | --------------- |
| `from`  | ISO 8601 | last 30 days | Window start    |
| `to`    | ISO 8601 | now          | Window end      |
| `limit` | integer  | `50`         | Max rows (1-50) |

Returns a `conversations` array of the most expensive threads, each with `conversation_id`, `agent_id`, `agent_name`, `model`, `total_tokens`, `total_cost_cents`, and `started_at` for drill-down.

### By feature

`GET /api/v1/insights/llm-spend/by-feature` accepts `from` and `to`. It aggregates spend by AI feature/channel (e.g. `voice`, `sms`, `inbox`, `kb`; events with no channel attributed appear as `unknown`) so you can see which feature is driving cost. It also returns the current `budget` config and any `threshold_breaches` — channels that have crossed their configured per-channel cap.

```json theme={null}
{
  "from": "2026-05-19T00:00:00.000Z",
  "to": "2026-06-18T00:00:00.000Z",
  "currency": "USD",
  "timezone": "America/New_York",
  "total_cents": 184200,
  "budget": {
    "monthly_cap_cents": 500000,
    "daily_cap_cents": 50000,
    "channel_thresholds": { "voice": 50000 },
    "alert_at_percent": 80
  },
  "features": [
    { "channel": "voice", "total_cost_cents": 92000, "total_tokens": 1840000, "event_count": 3201 },
    { "channel": "inbox", "total_cost_cents": 61200, "total_tokens": 1120000, "event_count": 2104 }
  ],
  "threshold_breaches": [
    { "channel": "voice", "cents": 92000, "threshold_cents": 50000, "percent": 184.0 }
  ]
}
```

### Budget

`GET /api/v1/insights/llm-spend/budget` returns the current AI budget config. `PUT` updates it. All body fields are optional — omitted fields keep their current value (read-merge-write), and the server re-validates on write, so out-of-range values are dropped rather than persisted.

| Field                | Type              | Notes                                                                        |
| -------------------- | ----------------- | ---------------------------------------------------------------------------- |
| `monthly_cap_cents`  | integer \| `null` | Monthly cap. `null` clears it. Max 100,000,000,000                           |
| `daily_cap_cents`    | integer \| `null` | Hard per-day cap, drives the overview cap ring. `null` clears it             |
| `channel_thresholds` | object            | Per-channel caps in cents, keyed by channel slug (e.g. `{ "voice": 50000 }`) |
| `alert_at_percent`   | integer           | Soft-alert threshold, 1-100. Defaults to 80                                  |

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/insights/llm-spend/budget \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "daily_cap_cents": 50000, "channel_thresholds": { "voice": 50000 }, "alert_at_percent": 80 }'
```

## Agent ROI

Attributes outcome revenue to each agent and reports cost, attributed revenue, and margin.

| Method | Path                                    | Purpose                                                     |
| ------ | --------------------------------------- | ----------------------------------------------------------- |
| `GET`  | `/api/v1/insights/agent-roi`            | Per-agent cost, attributed revenue, and margin + org totals |
| `GET`  | `/api/v1/insights/agent-roi/timeseries` | Daily cost / revenue / margin trend                         |
| `GET`  | `/api/v1/insights/agent-roi/config`     | Current attribution config                                  |
| `PUT`  | `/api/v1/insights/agent-roi/config`     | Update the attribution config                               |

`GET /agent-roi` accepts `from`, `to`, and `limit` (default `50`, max `200`). `GET /agent-roi/timeseries` accepts `from`, `to`, and an optional `agentId` to scope the trend to a single agent.

Attribution is driven by a value-per-outcome config. `PUT /agent-roi/config` is a read-merge-write with two optional fields:

| Field                         | Type              | Notes                                                                     |
| ----------------------------- | ----------------- | ------------------------------------------------------------------------- |
| `default_outcome_value_cents` | integer \| `null` | Default revenue value per attributed outcome. `null` clears it            |
| `per_rubric_value_cents`      | object            | Per-rubric value overrides in cents, keyed by rubric id (max 500 entries) |

## Containment & resolution

Reports how often agents handle a conversation without a human handoff (containment), how often a conversation reaches a resolved status or passes a rubric outcome (resolution), and how often it escalates.

| Method | Path                                      | Purpose                                                              |
| ------ | ----------------------------------------- | -------------------------------------------------------------------- |
| `GET`  | `/api/v1/insights/containment`            | Per-agent containment, resolution, and escalation rates + org totals |
| `GET`  | `/api/v1/insights/containment/timeseries` | Daily containment / resolution trend                                 |

`GET /containment` accepts `from`, `to`, and `limit` (default `50`, max `200`). `GET /containment/timeseries` accepts `from`, `to`, and an optional `agentId`.

## Agent comparison & benchmarks

Ranks agents against each other and against the org baseline on the KPIs already computed above (cost, attributed revenue, margin, containment, resolution, escalation).

| Method | Path                                | Purpose                                                                    |
| ------ | ----------------------------------- | -------------------------------------------------------------------------- |
| `GET`  | `/api/v1/insights/agent-comparison` | Flat KPI table for a chosen set of agents + an org-average baseline        |
| `GET`  | `/api/v1/insights/agent-benchmarks` | Per-metric percentile, rank, and band for each agent vs the org population |

| Param      | Type     | Notes                                                                                                                                                    |
| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`     | ISO 8601 | Window start (defaults to last 30 days)                                                                                                                  |
| `to`       | ISO 8601 | Window end (defaults to now)                                                                                                                             |
| `agentIds` | string   | Comma-separated agent ids. **Required for `agent-comparison`** (2-200 ids). Optional for `agent-benchmarks` — omit to benchmark every agent with traffic |

## See also

* [Analytics API](/api-reference/analytics) — message-traffic analytics, deliverability, and scheduled reports
* [Billing API](/api-reference/billing) — wallet, usage, and invoices
