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

# Casino Analytics

> 10 REST endpoints powering the Cassino Analytics screen — KPIs, channel composition, rankings, players and heatmaps.

Casino Analytics is the operational analytics surface for casino operators on
Atlas. It exposes 10 REST endpoints that feed the **Cassino Analytics** screen
in the dashboard, all backed by ClickHouse Gold tables (no synchronous reads
of raw events). Endpoints are scoped to the authenticated organization and
filtered by brand.

## Authentication

All endpoints require a valid session JWT:

```
Authorization: Bearer <jwt>
```

Cross-organization brand access returns `403 brand_not_in_org`. The maximum
window allowed is 12 months + 31 days; larger ranges return `400
window_too_large`.

## Common Query Parameters

| Param        | Required | Default                      | Notes                                              |
| ------------ | -------- | ---------------------------- | -------------------------------------------------- |
| `from`       | yes      | `to - 30d`                   | `YYYY-MM-DD` (UTC day boundary) or RFC3339 instant |
| `to`         | yes      | today 23:59:59 UTC           | same format as `from`                              |
| `brand_id[]` | no       | all active brands of the org | repeat for multi-select                            |
| `timezone`   | no       | `America/Sao_Paulo`          | only used by `/volume-by-hour`                     |

## Endpoints

### Overview

`GET /v1/analytics/casino/overview`

Returns the 10 KPIs of the redesign + variations vs M-1 and vs D-1 (both
returned in a single response — frontend toggles without re-fetch).

**Query params**: `from`, `to`, `brand_id[]`, `variation` (`vs_m1`/`vs_d1`,
informational only).

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "kpi_row1": {
    "ggr": 142500.0,
    "stake": 854000.0,
    "hold_pct": 16.69,
    "rounds": 184320
  },
  "kpi_row2": {
    "ngr": 128400.0,
    "bonus_cost_pct": 9.89,
    "neg_ggr_accumulated": -2350.0,
    "average_ticket": 4.63,
    "rounds_per_player": 51.2,
    "players": 3600
  },
  "variations": {
    "vs_m1": {
      "kpi_row1": {
        "ggr": { "previous_value": 110000.0, "delta_pct": 29.55 },
        "stake": { "previous_value": 720000.0, "delta_pct": 18.61 },
        "hold_pct": { "previous_value": 15.27, "delta_pct": 9.27 },
        "rounds": { "previous_value": 158400.0, "delta_pct": 16.36 }
      },
      "kpi_row2": { "ngr": { "previous_value": 99000.0, "delta_pct": 29.7 } }
    },
    "vs_d1": { "kpi_row1": { "ggr": { "previous_value": 137200.0, "delta_pct": 3.86 } } }
  }
}
```

KPI formulas:

| KPI                | Formula                                    |
| ------------------ | ------------------------------------------ |
| GGR                | `sum(bets_amount) - sum(wins_amount)`      |
| Stake              | `sum(bets_amount)`                         |
| Hold %             | `GGR / Stake × 100`                        |
| Rounds             | `sum(rounds_count)`                        |
| NGR                | `sum(real_ggr) + sum(bonus_ggr)`           |
| Bonus Cost %       | `(-bonus_ggr) / real_ggr × 100`            |
| GGR Negativo Acum. | `sumIf(real_ggr, real_ggr < 0)`            |
| Ticket Médio       | `Stake / Rounds`                           |
| Rodadas/Jogador    | `Rounds / uniqMerge(active_players_state)` |
| Players            | `uniqMerge(active_players_state)`          |

### Channel Composition

`GET /v1/analytics/casino/channel-composition`

Returns 4 buckets (Slots, Live, Tables, Outros) with GGR/Stake/Hold/share +
Δpp vs M-1. Buckets always returned in fixed order, even when empty.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "rows": [
    { "bucket": "slots",  "ggr": 92000.0, "stake": 540000.0, "hold_pct": 17.04, "share_pct": 64.56, "delta_pp_vs_m1": 1.2 },
    { "bucket": "live",   "ggr": 28000.0, "stake": 180000.0, "hold_pct": 15.56, "share_pct": 19.65, "delta_pp_vs_m1": -0.4 },
    { "bucket": "tables", "ggr": 15500.0, "stake": 100000.0, "hold_pct": 15.5,  "share_pct": 10.88, "delta_pp_vs_m1": 0.5 },
    { "bucket": "outros", "ggr": 7000.0,  "stake": 34000.0,  "hold_pct": 20.59, "share_pct": 4.91,  "delta_pp_vs_m1": -1.3 }
  ]
}
```

Bucket mapping (case-sensitive on `game_type`):

* `Video Slots` → `slots`
* `Live Games` → `live`
* `Baccarat`, `Roulette`, `Blackjack`, `Table Games`, `Poker`, `Video Poker` → `tables`
* `Crash Games`, `Video Bingo`, `Other Games`, `Lottery`, `Fast Games`, `Instant Games`, `Scratch Card`, `Jackpots` → `outros`
* unknown → `outros` + `casino_unknown_game_type` warn log

### Top Games

`GET /v1/analytics/casino/top-games?limit=5`

Top N games by GGR DESC with tiebreaker `rounds_count DESC`. `limit` defaults
to 5, max 100 (returns 422 above).

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "limit": 5,
  "total_ggr": 142500.0,
  "rows": [
    { "game_ext_id": "pp_sweet_bonanza", "game_name": "Sweet Bonanza", "game_provider": "Pragmatic", "game_type": "Video Slots", "bucket": "slots", "ggr": 28400.0, "stake": 184000.0, "rounds": 32100, "share_pct": 19.93 }
  ]
}
```

### Rankings

`GET /v1/analytics/casino/rankings?tab=top_stake&limit=20&offset=0`

Game-level ranking with 4 tabs and optional filters.

**Query params**: `tab` (`top_stake`/`top_rounds`/`ggr_positive`/`ggr_negative`,
default `top_stake`), `provider[]` (multi), `game_type[]` (multi: `slots`/`live`/`tables`/`outros`),
`limit` (default 20, max 100), `offset` (default 0).

Invalid `tab` → `422 invalid_tab`. Invalid bucket in `game_type[]` →
`422 invalid_game_type_bucket`.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "tab": "top_stake",
  "total": 184,
  "limit": 20,
  "offset": 0,
  "rows": [
    { "game_ext_id": "pp_sweet_bonanza", "game_name": "Sweet Bonanza", "game_provider": "Pragmatic", "game_type": "Video Slots", "bucket": "slots", "ggr": 28400.0, "stake": 184000.0, "hold_pct": 15.43, "rounds": 32100 }
  ]
}
```

### Providers

`GET /v1/analytics/casino/providers?tab=top_ggr`

Provider-level ranking with 2 tabs.

**Query params**: `tab` (`top_ggr`/`top_volume`, default `top_ggr`),
`limit` (default 20, max 100), `offset`.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "tab": "top_ggr",
  "total": 14,
  "limit": 20,
  "offset": 0,
  "rows": [
    { "game_provider": "Pragmatic", "ggr": 64200.0, "stake": 388000.0, "hold_pct": 16.55, "rounds": 84200 },
    { "game_provider": "Evolution", "ggr": 28100.0, "stake": 180000.0, "hold_pct": 15.61, "rounds": 12500 }
  ]
}
```

### Players Summary

`GET /v1/analytics/casino/players-summary`

Active players + churn rolling D7/D14/D30 + sessions per player.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "total_players": 3600,
  "new_players": 480,
  "returning_players": 3120,
  "sessions_per_player": 4.2,
  "churn": {
    "d7_pct": 22.4,
    "d14_pct": 12.1,
    "d30_pct": 4.6,
    "snapshot_age_days": 0,
    "has_snapshot": true
  }
}
```

`snapshot_age_days > 1` indicates the daily refreshable MV has not run
recently. Frontends should show a soft warning. `has_snapshot=false` means no
churn data has been computed yet (first-time setup or pipeline failure).

### Volume by Hour

`GET /v1/analytics/casino/volume-by-hour?game_type=slots&timezone=America/Sao_Paulo`

24 buckets in the requested timezone (default BRT). Response always contains
exactly 24 rows; hours without data return zeros.

**Query params**: `game_type` (single bucket: `slots`/`live`/`tables`/`outros`),
`timezone` (default `America/Sao_Paulo`).

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "timezone": "America/Sao_Paulo",
  "game_type_bucket": "slots",
  "rows": [
    { "hour": 0,  "stake": 4200.0,  "ggr": 720.0,  "hold_pct": 17.14, "rounds": 920 },
    { "hour": 19, "stake": 38400.0, "ggr": 6450.0, "hold_pct": 16.8,  "rounds": 8400 }
  ]
}
```

### GGR/NGR Accumulated

`GET /v1/analytics/casino/ggr-ngr-accumulated`

Daily cumulative series for GGR and NGR, with M-1 baseline aligned by
day-of-period index.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "points": [
    { "date": "2026-04-01", "ggr_acc": 4800.0,  "ngr_acc": 4320.0,  "prev_ggr_acc": 3600.0, "prev_ngr_acc": 3240.0 },
    { "date": "2026-04-29", "ggr_acc": 142500.0, "ngr_acc": 128400.0, "prev_ggr_acc": 110000.0, "prev_ngr_acc": 99000.0 }
  ]
}
```

### New vs Returning

`GET /v1/analytics/casino/new-vs-returning`

Monthly breakdown for up to 12 months: `total_players` minus
`new_players` = `returning_players`.

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2025-05-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "rows": [
    { "event_month": "2026-03", "new_players": 520, "returning_players": 2940, "total_players": 3460 },
    { "event_month": "2026-04", "new_players": 480, "returning_players": 3120, "total_players": 3600 }
  ]
}
```

### Weekly Heatmap

`GET /v1/analytics/casino/weekly-heatmap`

7×24 matrix (day-of-week × hour BRT) of GGR. ClickHouse convention:
`day_of_week=1` is Monday, `day_of_week=7` is Sunday. Hour is BRT (0..23).

**Response (200)**:

```json theme={null}
{
  "period": { "from": "2026-04-01T00:00:00Z", "to": "2026-04-29T23:59:59Z" },
  "min": 0.0,
  "max": 18420.0,
  "matrix": [
    { "day_of_week": 1, "hour_brt": 0,  "ggr": 0.0 },
    { "day_of_week": 5, "hour_brt": 22, "ggr": 18420.0 }
  ]
}
```

`min` and `max` are pre-computed for direct gradient rendering on the client.

## Errors

| Code                       | Status | When                                          |
| -------------------------- | ------ | --------------------------------------------- |
| `unauthorized`             | 401    | Missing or invalid JWT                        |
| `brand_not_in_org`         | 403    | `brand_id[]` contains a brand outside the org |
| `window_too_large`         | 400    | `to - from > 12 months + 31 days`             |
| `invalid_period`           | 400    | `to < from` or unparseable dates              |
| `invalid_tab`              | 422    | `tab` outside the endpoint's allowed enum     |
| `invalid_variation`        | 422    | `variation` not in `vs_m1`/`vs_d1`            |
| `invalid_game_type_bucket` | 422    | `game_type[]` contains a non-bucket value     |
| `limit_too_large`          | 422    | `limit > 100`                                 |
| `dependency_unavailable`   | 503    | ClickHouse offline or query timed out         |

## Performance

* Latency target: P95 \< 1s for windows up to 31 days, \< 3s for 12 months.
* Server-side ClickHouse timeout: 30s. Excess returns `503`.
* Polling default off in the dashboard. When enabled, requests share the same
  brand/period scope and aborted on filter change.

## Related

* [Financial Reports](/guides/analytics/financial-reports) — Revenue layer
* [Player Behavior](/guides/analytics/player-behavior) — Engagement layer
* [Tracking Casino](/guides/events/tracking-casino) — Event prerequisites
