Read KPIs
The KPI API returns metrics about the players linked to you, computed by PlayerPal from your activity, your facts and your Links. Every endpoint is a read-only GET under /v1/kpis. Your organisation is taken from your credential: there is no party parameter, and you can never query another partner's numbers. Metric definitions are in Metrics.
Endpoint map
The catalogue and own-tier KPIs serve operators and game providers. The overview also serves affiliates.
| Group | Endpoint | Use it when |
|---|---|---|
| Own KPIs | GET /v1/kpis/catalogue | You need the KPI IDs your party type can read |
GET /v1/kpis/own | Charting one metric by day, week or month | |
GET /v1/kpis/breakdown | Ranking markets, games or counterparts for one metric | |
GET /v1/kpis/overview | Showing every overview KPI with availability and trend in one call | |
| Comparison | GET /v1/kpis/compare | Comparing your players with the rest of the PlayerPal network |
GET /v1/kpis/compare/summary | Every comparison KPI for a period in one call | |
GET /v1/kpis/benchmarks | Comparing your totals with anonymous peers of your type | |
GET /v1/kpis/cohorts/retention | Monthly first-play cohorts and a pooled network curve | |
GET /v1/kpis/funnels/link | Seeing how Links turn into play | |
| Insights | GET /v1/kpis/intelligence/insights, /insights/{id} | Ranked, explained changes and opportunities |
GET /v1/kpis/intelligence/ask, /suggestions | Mapping a short question to a fixed analysis | |
| Explore | GET /v1/kpis/explore/* | Building detailed analytics views with filters |
Every KPI call is recorded in PlayerPal's audit log with your organisation and client. Each endpoint's parameters are in the API reference.
1List your catalogue
Start here. The catalogue lists the own-tier KPIs your party type can read, with each KPI's unit, the grains it supports, whether it takes a market filter (markets) and whether the account floor applies (account_floor).
| KPI | Unit | Who |
|---|---|---|
own.rounds, own.active_accounts | count | Operator, provider |
own.bet_eur, own.win_eur, own.ggr | EUR | Operator, provider |
own.linked_players, 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 |
HEADERS=(); while IFS= read -r h; do HEADERS+=("$h"); done < <(sign GET /v1/kpis/catalogue "" "")
curl -sS "$API/v1/kpis/catalogue" -H "${HEADERS[0]}" -H "${HEADERS[1]}" -H "${HEADERS[2]}"
# 200 {"kpis": [{id, label, unit, ...}]}
{
"kpis": [
{ "id": "own.rounds", "label": "Rounds", "unit": "count",
"markets": true, "grains": ["day", "week", "month"],
"account_floor": true },
{ "id": "own.new_links", "label": "New links", "unit": "count",
"markets": false, "grains": ["day", "week", "month"],
"account_floor": false }
]
}
2Read one KPI
own returns one KPI as a series, with the total, the previous period and the change between them.
fromandtoareYYYY-MM-DD, UTC and inclusive, and the range must be under 400 days.grainisday(default),week(ISO weeks from Monday) ormonth.marketis ISO 3166-1 alpha-2, for exampleMT, and works only for KPIs withmarkets: true.
Every period in the range is returned. A period with no activity is 0, or null for ratios and latencies. previous is the period of equal length just before from, and previous_series aligns it with series for overlays. delta_pct is null when either total is missing or the previous total is 0.
kpistringKPI id from the catalogue.fromstringStart date (YYYY-MM-DD).tostringEnd date (YYYY-MM-DD).grainstringTime grain.marketstringFilter: market.
You get 200 with one series entry for every week in the range. A week backed by too few accounts shows "suppressed": true.
QUERY='from=2026-09-01&grain=week&kpi=own.rounds&to=2026-09-30' # already sorted and encoded
HEADERS=(); while IFS= read -r h; do HEADERS+=("$h"); done < <(sign GET /v1/kpis/own "$QUERY" "")
curl -sS "$API/v1/kpis/own?$QUERY" -H "${HEADERS[0]}" -H "${HEADERS[1]}" -H "${HEADERS[2]}"
{
"kpi": "own.rounds", "label": "Rounds", "unit": "count",
"from": "2026-09-01", "to": "2026-09-30", "grain": "week",
"market": null,
"series": [
{ "period": "2026-08-31", "value": 1240 },
{ "period": "2026-09-07", "value": null, "suppressed": true },
{ "period": "2026-09-14", "value": 1310 },
{ "period": "2026-09-21", "value": 1290 },
{ "period": "2026-09-28", "value": 1280 }
],
"total": { "value": 5160 },
"previous": { "from": "2026-08-02", "to": "2026-08-31", "value": 4800 },
"previous_series": [
{ "value": 610 }, { "value": 1150 }, { "value": 1180 },
{ "value": 1220 }, { "value": 1240 }, { "value": 1200 }
],
"delta_pct": 0.075,
"generated_at": "2026-10-13T12:00:00.000Z"
}
3Break it down
breakdown takes the same parameters as own, except grain, plus dimension: 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. Breakdowns support activity KPIs only; others return 400 breakdown_not_supported.
The response holds the top 10 rows by value. For additive KPIs, each row has its share of the visible total and the remaining rows are folded into one Other (n) row. Rows under the account floor are never listed; they are counted in suppressed_rows.
kpistringKPI id from the catalogue.dimensionstringBreakdown dimension.One of:marketgamepartnerfromstringStart date (YYYY-MM-DD).tostringEnd date (YYYY-MM-DD).marketstringFilter: market.
QUERY='dimension=market&from=2026-09-01&kpi=own.rounds&to=2026-09-30' # already sorted and encoded
HEADERS=(); while IFS= read -r h; do HEADERS+=("$h"); done < <(sign GET /v1/kpis/breakdown "$QUERY" "")
curl -sS "$API/v1/kpis/breakdown?$QUERY" -H "${HEADERS[0]}" -H "${HEADERS[1]}" -H "${HEADERS[2]}"
{
"kpi": "own.rounds", "label": "Rounds", "unit": "count",
"dimension": "market",
"from": "2026-09-01", "to": "2026-09-30", "market": null,
"rows": [
{ "key": "MT", "label": "MT", "value": 3100, "share": 0.6055 },
{ "key": "DE", "label": "DE", "value": 1400, "share": 0.2734 },
{ "key": "FI", "label": "FI", "value": 620, "share": 0.1211 }
],
"suppressed_rows": 2
}
4Compare with the network
compare takes a cmp.* KPI and a closed period: grain=month with period=2026-09, or grain=week with period=2026-W38. An open or future period returns 400 period_not_closed.
youis your players' median for per-player KPIs, or your ratio for party KPIs.indexis you ÷ network median × 100, andbandis the network quartile your value falls in.- Network size and party counts are given only as bands, never exact numbers.
- With a
market, a market too thin to publish falls back to the global figure withfallback: "global".
The network side is published only when the rest of the network spans at least 5 parties of your type and 50 accounts, and no single party holds more than 50% of the measure (or two more than 75%).
benchmarks compares your totals with anonymous peers of your type, using the same period, grain and market. It takes own.rounds, own.bet_eur, own.ggr or own.active_accounts (global or per market), or own.linked_players, own.ingest_acceptance or own.link_conversion (global only). A segment is published only with at least 6 parties in it, you plus 5 others, and no dominant peer.
{
"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": "2026-10-01T06:00:00.000Z"
}
{
"kpi": "own.bet_eur", "period": "2026-09", "grain": "month",
"from": "2026-09-01", "to": "2026-09-30",
"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": "2026-10-01T06:00:00.000Z"
}
5Handle suppression and errors
PlayerPal withholds any cell that describes too few people, and the rule is the same for every caller. See Privacy thresholds.
- Own KPIs and breakdowns: for KPIs with
account_floor: true, a cell backed by fewer than 50 distinct Accounts comes back as"value": null, "suppressed": true. Show it as withheld, never as zero. - Comparisons and benchmarks: a withheld value has
suppressed: trueand areason.
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 | Your players' activity elsewhere spans too few parties, or one dominates it |
not_computed | No data for that period yet |
| Status | Codes |
|---|---|
400 | kpi_unknown, kpi_not_available_for_party, date_range_invalid, date_range_too_long, grain_invalid, market_invalid, market_not_supported, dimension_invalid, breakdown_not_supported, month_range_invalid, month_range_too_long, period_not_closed, filter_invalid |
403 | partner_scope_missing: the credential lacks kpis.read |
404 | Codes ending in _not_found, such as insight_not_found |
502, 503 | kpi_backend_error, kpi_backend_not_configured. Retry later |
{
"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": null,
"index": null, "band": null,
"suppressed": true, "reason": "peer_floor",
"computed_at": "2026-10-01T06:00:00.000Z"
}
{
"statusCode": 400,
"message": "date_range_too_long",
"error": "Bad Request"
}
Overview, cohorts, insights and Explore
Overview. GET /v1/kpis/overview?from=…&to=… returns every overview KPI for your audience, each with value, trend, source, lastUpdated and a dataAvailability that is never faked as zero: available, requires_feed (with a requirement naming the missing feed), not_applicable (for example commercial KPIs for studios) or suppressed. It also returns chart panels, each with its own availability. Operators see their own players, studios see players of their games, and affiliates see only their verified-attributed players.
Cohorts and funnels. /cohorts/retention takes from and to as YYYY-MM, defaults to the last 12 closed months and allows at most 24. /funnels/link takes YYYY-MM-DD dates; the whole funnel is suppressed when its first step is under the account floor.
Insights. /intelligence/insights returns ranked insights and an executive summary from fixed rules and statistical tests. No language model computes or writes them. /intelligence/ask takes q (up to 300 characters are used) and routes it to one fixed analysis; it never runs free-form queries. See Insights.
Explore. /v1/kpis/explore/options, performance, summary, dimension (with dim=game, market or partner), players, retention, segments, funnel, game, comparison and benchmark-history back detailed analytics views. Each takes from and to, plus optional game, market and partner filters. Call explore/options first: it lists only filter values with at least 50 players. A malformed filter, or a partner that is not one of your counterparts, returns 400 filter_invalid.