Partner KPI API
Your KPIs, covering only the players linked to you. Authenticate with OAuth client credentials or PP-HMAC-SHA256 (hmac-signing.md) using a credential with scope kpis.read. Your party is taken from the credential; there is no party parameter. Definitions are in docs/kpis.md.
GET /v1/kpis/catalogue
Returns the KPIs your party type can read.
{ "kpis": [ { "id": "own.rounds", "label": "Rounds", "unit": "count", "markets": true,
"grains": ["day", "week", "month"], "account_floor": true } ] }
GET /v1/kpis/own
| Param | Required | Notes |
|---|---|---|
kpi | yes | An ID from the catalogue |
from, to | yes | YYYY-MM-DD (UTC), inclusive, under 400 days |
grain | no | day (default), week (ISO weeks starting Monday) or month |
market | no | ISO 3166-1 alpha-2, e.g. MT. Only for KPIs with markets: true |
{
"kpi": "own.rounds", "label": "Rounds", "unit": "count",
"from": "2026-09-01", "to": "2026-09-30", "grain": "week", "market": "MT",
"series": [ { "period": "2026-08-31", "value": 1240 },
{ "period": "2026-09-07", "value": null, "suppressed": true } ],
"total": { "value": 5120 },
"previous": { "from": "2026-08-02", "to": "2026-08-31", "value": 4800 },
"delta_pct": 0.0667,
"generated_at": "2026-10-13T12:00:00.000Z"
}
- Every period in the range is returned. A period with no activity has value
0; for ratios and latencies it isnull. - A cell is
suppressedwhen fewer than 50 distinct accounts are behind it (ADR 0026). This applies to KPIs withaccount_floor: true. previousis the period of the same length immediately beforefrom.delta_pctisnullwhen either total is missing or the previous total is0.
Errors (400): kpi_unknown, kpi_not_available_for_party, date_range_invalid, date_range_too_long, grain_invalid, market_invalid and market_not_supported. 503 kpi_backend_not_configured and 502 kpi_backend_error are server-side problems.
Every call is recorded as a kpi.api.accessed domain event (party, client, KPI).
Own-tier KPIs (v1)
| ID | Unit | Who | Account floor | Market |
|---|---|---|---|---|
own.rounds | count | operator, provider | ✓ | ✓ |
own.bet_eur / own.win_eur / own.ggr | EUR | operator, provider | ✓ | ✓ |
own.active_accounts | count | operator, provider | ✓ | ✓ |
own.linked_players | count (distinct in period) | operator, provider | ✓ | |
own.new_links / own.unlinks | count | operator, provider | ||
own.link_intents / own.link_conversion | count / ratio | operator | ||
own.points_issued | points | operator, provider | ✓ | |
own.ingest_acceptance | ratio | operator, provider | ||
own.batch_latency_p95 | seconds | provider |
GET /v1/kpis/overview
This is the partner overview (Spec v0.1 §13), computed from the canonical facts (ingest.md). Pass from and to as YYYY-MM-DD. The previous period is the one of the same length immediately before.
Every KPI in the registry (analytics/overview/kpi-registry.ts) comes back typed:
{ "id": "net_revenue", "name": "Net revenue (NGR)", "description": "…", "unit": "eur",
"value": 841.54, "trend": { "previous": 1003.2, "delta": -0.161, "kind": "relative", "direction": "up" },
"dataAvailability": "available", "source": ["operator_commercial"], "requirement": null,
"lastUpdated": "2026-10-13T12:40:05.000Z" }
dataAvailability is never faked as a zero:
| Value | Meaning |
|---|---|
available | Computed from delivered facts |
requires_feed | requirement names the missing feed, e.g. "Requires operator commercial feed" |
not_applicable | Not shown to this audience, e.g. commercial and reward KPIs for studios (§13.2) |
suppressed | Fewer than 50 active players in the range |
Which players count:
- Operators: their own players.
- Studios: players of their games.
- Affiliates: only their verified-attributed players (§13.3).
Ratio trends are in percentage points; other trends are relative changes.
Retention uses fixed observation windows: D1 = days 1–7, D7 = days 7–13, D30 = days 30–36. Only new players whose whole window has elapsed are counted, so recent cohorts aren't biased low.
panels carries the chart data, each with its own availability:
activitycohorts(weekly, or monthly when weekly cohorts would fall under the floor)mixgeo(markets under the floor fold into Other)segments(now and previous)campaigns(affiliates)rewardsmissionsreward_impressions(requires_feed: impressions are not a v1 feed)
GET /v1/kpis/breakdown
Same parameters as /own, plus dimension set to market, game or partner. A partner is one of your own integration counterparts: a studio if you are an operator, an operator if you are a studio. Activity KPIs only.
The response contains the top 10 rows by value, each with its share of the visible total (additive KPIs only), then Other (n). Rows under the account floor are never listed; they are counted in suppressed_rows.
GET /v1/kpis/cohorts/retention
Takes from and to as YYYY-MM, defaulting to the last 12 closed months. Each cohort is the month of a player's first round with you. retention[o] is the share of that cohort active again o months later. Cohorts under the account floor are suppressed.
network is the pooled retention of other parties of your type. It is only published when at least 5 parties contribute, there are at least 50 accounts, and no party dominates; otherwise network_reason says why.
GET /v1/kpis/funnels/link
Takes from and to as YYYY-MM-DD.
- Operators: link started → linked → played within 30 days.
- Studios: linked through your integrations → played your games within 30 days.
Each step gives its rate against the first step and its step_rate against the previous step. The whole funnel is suppressed when the first step is under the account floor.
/own also returns previous_series: the period before, aligned by position with series, so charts can overlay it.
Comparison and benchmark periods
Comparisons and benchmarks use fixed, closed periods only. grain=month with period=YYYY-MM, or grain=week with period=YYYY-Www (ISO week). An open or future period is rejected with period_not_closed. Results are recomputed every 6 hours for recent periods.
GET /v1/kpis/compare
Compares your players with the rest of the PlayerPal network: players of other parties of your type. See kpis.md, Network comparison KPIs.
| Param | Required | Notes |
|---|---|---|
kpi | yes | A cmp.* ID (table below) |
period, grain | yes | See above |
market | no | If that market is too thin to publish, you get the global figure with fallback: "global" |
{
"kpi": "cmp.bet_per_player", "period": "2026-09", "grain": "month",
"from": "2026-09-01", "to": "2026-09-30", "market": null, "fallback": null,
"you": 45,
"network": { "baseline": "rest_of_network", "p25": 20, "p50": 36, "p75": 52, "mean": 38.4,
"size_band": "100-999", "parties_band": "5-9" },
"index": 125, "band": "p50-p75",
"suppressed": false, "reason": null, "computed_at": "…"
}
youis your players' median for per-player KPIs, or your ratio for party KPIs.indexisyou ÷ network median × 100.bandis the quartile of the network distribution your value falls in.- Network size and party count are given only as bands.
reason (when anything is withheld):
| Reason | Meaning |
|---|---|
peer_floor | The rest of the network spans fewer than 5 parties of your type |
account_floor | The rest of the network has fewer than 50 accounts |
dominance | One party has more than 50% of the measure, or two have more than 75% |
y_floor | Your own population is under 50 accounts; the network is still shown |
elsewhere_floor | Cross-party KPI where your players' activity elsewhere spans fewer than 5 parties, or is dominated by one |
not_computed | No data for that period yet |
cmp.* KPI | Who | Kind |
|---|---|---|
cmp.active_days, cmp.rounds_per_player, cmp.bet_per_player, cmp.ggr_per_player | operator, provider | Your players' median vs the network's player distribution |
cmp.points_per_player | operator | As above (global only) |
cmp.avg_stake, cmp.payout_ratio, cmp.churn_rate | operator, provider | Your ratio vs other parties' ratios |
cmp.link_conversion | operator | As above (global only) |
cmp.multi_homing, cmp.overlap_rate, cmp.share_of_wallet | operator, provider | Cross-party (elsewhere_floor applies) |
GET /v1/kpis/compare/summary
Takes period, grain and optionally market. Returns every cmp.* KPI for your party type in one response, as { …period fields, kpis: [ { kpi, fallback, you, network, index, band, suppressed, reason } ] }.
GET /v1/kpis/benchmarks
Compares you with anonymous peers of your party type. Takes the same period, grain and market parameters.
{ "kpi": "own.bet_eur", "period": "2026-09", "grain": "month", "market": null, "fallback": null,
"peers": { "party_type": "OPERATOR", "count_band": "5-9", "p25": 1200, "p50": 1800, "p75": 2600, "mean": 1950 },
"band": "p0-p25", "suppressed": false, "reason": null, "computed_at": "…" }
Benchmarkable KPIs: own.rounds, own.bet_eur, own.ggr and own.active_accounts. These are global and per market. own.linked_players, own.ingest_acceptance and own.link_conversion are global only. A segment is published only when at least 6 parties are in it (you plus at least 5 others) and no peer dominates it.
Staff
GET /v1/staff/kpis/own?party=<public party id>&kpi=… takes the same parameters. It is not floored and is audited as kpi.api.accessed with actor staff.
Analytics workstation (/v1/kpis/explore/*)
These endpoints back the partner portal's Analytics pages. Every one is scoped to the authenticated party. Each takes from and to (YYYY-MM-DD) and these optional filters:
| Filter | Format | Example |
|---|---|---|
game | game ref | orbit-breaker |
market | ISO 3166-1 alpha-2 | MT |
partner | counterpart public id | pp_opr_… |
A filter value the party has no data for returns 400 filter_invalid.
| Endpoint | Returns |
|---|---|
explore/options | The games, markets and counterparts this party can filter by. Only values with at least 50 players are offered. |
explore/performance | Engagement KPIs (MetricValue: value, previous value, change, formula, sample size, availability class, source, freshness) and a series per period against the previous period. |
explore/dimension?dim=game|market|partner | One row per entity: active players and change, new players, sessions, minutes, repeat rate, D7/D30 (players returning to that entity), game GGR (bet − win) per player, and an 8-week series. Also deterministic highlights. Rows under 50 players are left out and counted in suppressed. |
explore/players | Lifecycle KPIs, a 7-state mix by week, and the flows between the previous and current period. Flows under 50 players are left out and counted. |
explore/retention?months= | Monthly first-play cohorts × D1…D90 (DN = played again in days N to N+6). A cell is shown only once the whole cohort has had its window. Also your curve against the pooled network curve. |
explore/benchmark-history | Your stored monthly comparisons against the network quartiles. Unpublished segments carry no network values. |
Network curve. It pools every other party of your type, excluding you. It is returned only with at least 5 peers and no peer above 50% of the pooled cohort. Otherwise the response carries available: false and a reason. No peer is ever returned on its own.
Lifecycle states. Each player is classified by their own activity with you. The first matching state wins, in this order:
| State | Rule |
|---|---|
| New | First play in the last 14 days |
| Reactivated | Playing in the last 14 days after 30+ quiet days |
| Highly engaged | 8+ active days in the last 28, not slowing down |
| Declining | Playing, on fewer than half the days of the 14 before |
| Active | Played in the last 14 days |
| At risk | Last played 15–30 days ago |
| Dormant | Last played 31–119 days ago |
compare/summary also returns position: { favourable, assessed }: the metrics in the better half of the network, out of those where a direction is defined.
Insights (/v1/kpis/intelligence/insights?from&to)
These are ranked, explainable insights plus an executive summary. They are computed by fixed rules and statistical tests over the KPI engine's outputs, so every privacy floor already applies. No language model computes or writes them.
Detectors
| Detector | What it reports |
|---|---|
| Change | A material move against the previous period: at least 8% relative, or 2pp for rates. |
| Driver | The market, game or counterpart carrying at least 30% of the net change in active players. |
| Retention gap / strength | A segment whose D7 retention is at most 0.75×, or at least 1.25×, that of your other segments. |
| Portfolio opportunity | A game with above-median retention but below-median reach. |
| Benchmark | A top- or bottom-quartile position against anonymous peers. |
| Anomaly | One of the last 14 days far outside the 14 days before it (robust z of at least 3.5, and a change of at least 25%). |
Confidence
Confidence is 1 − p from a two-sided test:
- Counts: a Poisson test.
- Rates: a two-proportion z-test against the rate's real base.
- Rates with an unknown base: capped at 0.85.
- Rates whose base is under the 50-player floor: never reported.
Ranking. Insights are ranked by magnitude × population × business weight × confidence × freshness. primary holds three of them: a positive, a risk and an opportunity.
Wording. The text describes association and concentration only. Tests reject causal wording.