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

# Dashboard Data

> Aggregated data endpoints powering the platform dashboards.

The Dashboard Data API provides pre-aggregated analytics for the platform's monitoring dashboards. Data is aggregated via SQL to avoid loading large result sets.

## GET /api/v1/dashboard-data

Retrieve dashboard data by type.

### Query Parameters

| Parameter        | Type    | Required                      | Default                                                                    | Description                                                                                                                                                                                                                                                                                                                                                               |
| ---------------- | ------- | ----------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`           | string  | No                            | `summary`                                                                  | Data type to retrieve (see table below)                                                                                                                                                                                                                                                                                                                                   |
| `days`           | integer | No                            | `7`                                                                        | Lookback window in days (1-365), used by time-based types                                                                                                                                                                                                                                                                                                                 |
| `modelId`        | string  | No                            | --                                                                         | Algorithm model ID (only for `model_metrics_trend`)                                                                                                                                                                                                                                                                                                                       |
| `groupBy`        | string  | No                            | `channel` for `offer_performance_grouped`, `null` for `score_distribution` | Grouping dimension. Accepts `channel`, `category`, or `segment` for `offer_performance_grouped`; `channel`, `offer`, or `category` for `score_distribution`                                                                                                                                                                                                               |
| `filterId`       | string  | No                            | --                                                                         | Filter by dimension ID (only for `score_distribution`)                                                                                                                                                                                                                                                                                                                    |
| `segmentId`      | string  | No                            | --                                                                         | Restrict the result to customers in the given segment. Accepted by `acceptance_rate`, `offer_performance`, `offer_performance_grouped`, `channel_effectiveness`, `daily_trend`, `revenue_trend`, `selection_frequency`, and `why_not_ranked`. Returns `{"data": [], "warning": "..."}` when the segment does not exist or the segment view has not been materialized yet. |
| `channelId`      | string  | No                            | --                                                                         | Restrict `selection_frequency` to decisions made on a specific channel.                                                                                                                                                                                                                                                                                                   |
| `categoryId`     | string  | No                            | --                                                                         | Restrict `selection_frequency` to offers in a specific category.                                                                                                                                                                                                                                                                                                          |
| `decisionFlowId` | string  | No                            | --                                                                         | Restrict `selection_frequency` to decisions produced by a specific decision flow.                                                                                                                                                                                                                                                                                         |
| `offerId`        | string  | Required for `why_not_ranked` | --                                                                         | Target offer for the why-not analysis.                                                                                                                                                                                                                                                                                                                                    |
| `baselineDays`   | integer | No                            | same as `days`                                                             | For `anomaly_candidates` — the length of the prior-period window compared against the current window.                                                                                                                                                                                                                                                                     |

### Data Types

| Type                        | Description                                                                                                                                                                        |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `summary`                   | High-level counts: active offers, channels, experiments, journeys, triggers, pending approvals                                                                                     |
| `acceptance_rate`           | Acceptance rate per offer (impressions vs positive outcomes) over `days` window                                                                                                    |
| `offer_performance`         | Top 20 active offers with impressions, conversions, revenue, and conversion rate                                                                                                   |
| `channel_effectiveness`     | Impressions, conversions, and effectiveness rate per channel                                                                                                                       |
| `budget_burn`               | Budget allocation utilization: allocated, spent, remaining, burn rate                                                                                                              |
| `funnel`                    | Offer funnel stages: total offers, active, with creatives, total creatives                                                                                                         |
| `attribution_summary`       | Attribution results grouped by model with conversion counts and total value                                                                                                        |
| `daily_trend`               | 7-day trend of impressions and conversions                                                                                                                                         |
| `model_metrics_trend`       | AUC trend and feature importance from algorithm models (accepts `modelId`)                                                                                                         |
| `latency_trend`             | 7-day p50/p99 latency percentiles from run execution data                                                                                                                          |
| `offer_performance_grouped` | Offer performance grouped by `channel` or `category` (accepts `groupBy`), summed from **daily** interaction summaries within the `days` window; conversion rate is clamped to 100% |
| `revenue_trend`             | Daily revenue trend from interaction summaries over `days` window                                                                                                                  |
| `policy_impact`             | Active suppressions grouped by policy type with counts                                                                                                                             |
| `score_distribution`        | Propensity score histogram from decision traces (accepts `groupBy` and `filterId`)                                                                                                 |
| `degraded_scoring`          | Count of degraded scoring events (fallback scoring used due to model errors or timeouts)                                                                                           |
| `selection_frequency`       | Per-offer eligible / scored / selected counts, selection rate, average rank, and rank distribution over the window                                                                 |
| `anomaly_candidates`        | Core-metric anomalies (acceptance rate, revenue, degraded scoring rate) vs a prior-period baseline — stateless; no alert firing                                                    |
| `why_not_ranked`            | For a target `offerId`, breakdown of how often the offer was eligible but not selected and the top reasons                                                                         |

***

### Response -- `summary`

```json theme={null}
{
  "activeOffers": 15,
  "activeChannels": 4,
  "activeExperiments": 2,
  "totalJourneys": 3,
  "activeTriggers": 5,
  "pendingApprovals": 1
}
```

<Note>
  All entity counts (`activeOffers`, `activeChannels`, `activeTriggers`) exclude
  soft-deleted rows, so they always match what the corresponding list pages show.
  Soft-delete only sets a `deletedAt` timestamp — it does **not** change `status`
  — so these counts filter on `deletedAt IS NULL` in addition to `status`.
</Note>

### Response -- `acceptance_rate`

```json theme={null}
{
  "data": [
    {
      "offerId": "off_001",
      "offerName": "Premium Card Upgrade",
      "impressions": 1250,
      "positive": 87,
      "negative": 45,
      "acceptanceRate": 0.0696
    }
  ],
  "total": 15,
  "period": {
    "days": 7,
    "since": "2026-03-23T00:00:00.000Z"
  }
}
```

### Response -- `offer_performance`

```json theme={null}
{
  "data": [
    {
      "offerId": "off_001",
      "name": "Premium Card Upgrade",
      "priority": 90,
      "creativeCount": 3,
      "impressions": 1250,
      "conversions": 87,
      "revenue": 4350.00,
      "conversionRate": "7.0"
    }
  ]
}
```

### Response -- `channel_effectiveness`

```json theme={null}
{
  "data": [
    {
      "channelId": "ch_001",
      "channelName": "Marketing Email",
      "impressions": 5000,
      "conversions": 350,
      "effectiveness": "7.0"
    }
  ]
}
```

### Response -- `budget_burn`

```json theme={null}
{
  "data": [
    {
      "offerId": "off_001",
      "allocated": 10000,
      "spent": 4500,
      "remaining": 5500,
      "burnRate": "45.0"
    }
  ]
}
```

### Response -- `funnel`

```json theme={null}
{
  "stages": [
    { "label": "Total Offers", "count": 25 },
    { "label": "Active Offers", "count": 15 },
    { "label": "With Creatives", "count": 12 },
    { "label": "Creatives", "count": 34 }
  ]
}
```

<Note>
  Every funnel stage counts live entities only (`deletedAt IS NULL`). "Total
  Offers" is all non-deleted offers, not literally every row ever created —
  soft-deleted offers and creatives are excluded from all four stages so the
  funnel stays consistent with the offers and creatives list pages. The
  `offer_performance` list is filtered the same way.
</Note>

### Response -- `attribution_summary`

```json theme={null}
{
  "data": [
    {
      "model": "last_touch",
      "conversions": 150,
      "totalValue": 45000.00,
      "avgValue": 300.00
    }
  ],
  "totalConversions": 150
}
```

### Response -- `daily_trend`

```json theme={null}
{
  "data": [
    { "name": "Mar 24", "impressions": 340, "conversions": 22 },
    { "name": "Mar 25", "impressions": 420, "conversions": 31 },
    { "name": "Mar 26", "impressions": 380, "conversions": 28 }
  ]
}
```

### Response -- `model_metrics_trend`

```json theme={null}
{
  "aucTrend": [
    { "name": "Mar 15", "auc": 0.82 },
    { "name": "Mar 16", "auc": 0.84 }
  ],
  "featureImportance": [
    { "name": "tenure_months", "value": 0.32 },
    { "name": "monthly_spend", "value": 0.28 }
  ],
  "modelCount": 2,
  "modelName": "Propensity to Buy"
}
```

### Response -- `latency_trend`

```json theme={null}
{
  "data": [
    { "name": "Mon", "p50": 45, "p99": 220 },
    { "name": "Tue", "p50": 42, "p99": 195 }
  ],
  "hasRealData": true
}
```

### Response -- `offer_performance_grouped`

```json theme={null}
{
  "groups": [
    {
      "groupName": "Marketing Email",
      "groupId": "ch_001",
      "offers": [
        {
          "offerId": "off_001",
          "name": "Premium Card Upgrade",
          "impressions": 1250,
          "conversions": 87,
          "revenue": 4350.00,
          "conversionRate": "7.0"
        }
      ]
    }
  ],
  "groupBy": "channel"
}
```

### Response -- `revenue_trend`

```json theme={null}
{
  "data": [
    { "name": "Mar 1", "revenue": 1200.50 },
    { "name": "Mar 2", "revenue": 980.00 }
  ],
  "hasRealData": true
}
```

### Response -- `policy_impact`

```json theme={null}
{
  "data": [
    { "policyType": "frequency_cap", "count": 45 },
    { "policyType": "cooldown", "count": 12 }
  ],
  "totalActive": 57
}
```

### Response -- `score_distribution`

```json theme={null}
{
  "data": [
    { "name": "0.0-0.1", "count": 15 },
    { "name": "0.1-0.2", "count": 22 },
    { "name": "0.2-0.3", "count": 38 },
    { "name": "0.3-0.4", "count": 45 },
    { "name": "0.4-0.5", "count": 52 },
    { "name": "0.5-0.6", "count": 67 },
    { "name": "0.6-0.7", "count": 48 },
    { "name": "0.7-0.8", "count": 35 },
    { "name": "0.8-0.9", "count": 20 },
    { "name": "0.9-1.0", "count": 8 }
  ],
  "totalScores": 350,
  "hasRealData": true,
  "groupBy": null,
  "dimensionBuckets": [
    {
      "name": "ch_001",
      "dimName": "Marketing Email",
      "count": 200,
      "avgScore": 0.542
    }
  ]
}
```

The `score_distribution` type accepts `groupBy` values of `channel`, `offer`, or `category` to break down scores by dimension. Use `filterId` to show only scores for a specific dimension value.

### Response -- `degraded_scoring`

```json theme={null}
{
  "degradedCount": 3,
  "totalCount": 1250,
  "days": 7,
  "hasData": true
}
```

Returns the count of recommend calls where the scoring engine fell back to degraded mode (e.g., model timeout, missing data). `totalCount` is the total number of scored requests in the period.

***

### Response -- `selection_frequency`

```json theme={null}
{
  "data": [
    {
      "offerId": "off_premium_card",
      "offerName": "Premium Card Upgrade",
      "eligibleCount": 340,
      "scoredCount": 340,
      "selectedCount": 48,
      "selectionRate": 0.141,
      "avgRank": 2.3,
      "rankDistribution": [48, 112, 85, 42, 30, 15, 6, 2, 0, 0]
    }
  ],
  "totalTraces": 412,
  "period": { "days": 7, "since": "2026-03-23T00:00:00.000Z" }
}
```

Aggregates `DecisionTrace.scoringResults[]` and `selectedOffers[]` across the window. The trace sample is capped at 10,000 rows per call for latency. `rankDistribution` is a 10-element array covering ranks 1..10; ranks beyond 10 are not tracked. Supports optional `channelId`, `categoryId`, `decisionFlowId`, and `segmentId` filters.

Example:

```bash theme={null}
curl -H "X-API-Key: $API_KEY" -H "X-Tenant-Id: $TENANT_ID" \
  "https://playground.kaireonai.com/api/v1/dashboard-data?type=selection_frequency&days=14&segmentId=seg_vip"
```

### Response -- `anomaly_candidates`

```json theme={null}
{
  "data": [
    {
      "metric": "acceptance_rate",
      "dimension": "offer",
      "dimensionKey": "off_premium_card",
      "dimensionName": "Premium Card Upgrade",
      "baseline": 0.102,
      "current": 0.184,
      "deltaPct": 80.4,
      "zscore": 4.3,
      "severity": "critical"
    }
  ],
  "period": {
    "currentDays": 7,
    "baselineDays": 7,
    "since": "2026-03-23T00:00:00.000Z"
  }
}
```

Severity is classified by the larger of the z-score magnitude and the absolute percent change: `info` at `|z| >= 2` or `|%| >= 15`, `warning` at `|z| >= 3` or `|%| >= 30`, `critical` at `|z| >= 4` or `|%| >= 50`. Metrics covered: `acceptance_rate` (overall, per-offer, per-channel), `revenue` (per-offer), and `degraded_scoring_rate` (overall). This endpoint is stateless — it does not fire alerts; it surfaces candidates that a scheduler in a later phase may escalate.

Example:

```bash theme={null}
curl -H "X-API-Key: $API_KEY" -H "X-Tenant-Id: $TENANT_ID" \
  "https://playground.kaireonai.com/api/v1/dashboard-data?type=anomaly_candidates&days=7&baselineDays=7"
```

### Response -- `why_not_ranked`

```json theme={null}
{
  "offerId": "off_premium_card",
  "offerName": "Premium Card Upgrade",
  "timesEligible": 340,
  "timesSelected": 48,
  "missRate": 0.859,
  "missReasons": {
    "scoredTooLow": 220,
    "filteredByContactPolicy": 45,
    "filteredByQualification": 27,
    "beatenBy": [
      { "offerId": "off_gold_plus", "offerName": "Gold Plus", "count": 102 },
      { "offerId": "off_starter", "offerName": "Starter Card", "count": 48 }
    ]
  },
  "rankDistribution": [48, 112, 85, 42, 30, 15, 6, 2, 0, 0],
  "avgScore": 0.432,
  "period": { "days": 7, "since": "2026-03-23T00:00:00.000Z" },
  "sampleSize": 412,
  "sampleCap": 1000
}
```

For a given target offer, walks the most recent traces (capped at `sampleCap`) and attributes every miss to one of: contact-policy block, qualification rejection, or scored-but-outranked. When the offer was scored but not selected, the winning offer in that decision is counted toward the `beatenBy` list (top 5). The `offerId` query parameter is required; optional `segmentId` narrows the analysis to customers in that segment.

Example:

```bash theme={null}
curl -H "X-API-Key: $API_KEY" -H "X-Tenant-Id: $TENANT_ID" \
  "https://playground.kaireonai.com/api/v1/dashboard-data?type=why_not_ranked&offerId=off_premium_card&days=14"
```

### Segment dimension on existing performance endpoints

`acceptance_rate`, `offer_performance`, `offer_performance_grouped`, `channel_effectiveness`, `daily_trend`, and `revenue_trend` all accept an optional `segmentId` query parameter. When provided, the underlying aggregation is restricted to interactions from customers in the segment's materialized view.

`offer_performance_grouped&groupBy=segment` fans the result out by active segment instead of channel/category; each offer row is decorated with `segmentId` and `segmentName`.

If the segment is unknown or its view has not been materialized yet, the endpoint returns `{"data": [], "warning": "..."}` instead of an error, so dashboards can render a friendly notice.

***

### Error codes

| Code  | Reason                         |
| ----- | ------------------------------ |
| `400` | Unknown `type` value.          |
| `403` | Insufficient role permissions. |

## Role requirements

| Method | Minimum role |
| ------ | ------------ |
| GET    | `viewer`     |
