---
title: "Aggregate model performance for a sport"
method: GET
path: "/v1/performance/{sport}"
tags: ["performance"]
---

# Aggregate model performance for a sport

`GET /v1/performance/{sport}`

## Request

- Base URL: `https://api.predixsport.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.predixsport.com/v1/performance/{sport}`
- Auth: none declared

Returns headline metrics for the requested sport's match model. The ``source`` parameter is mutually exclusive — never a union, so a dashboard can never double-count the same match across two regimes:

• ``production`` — predictions that existed in our database BEFORE kick-off, graded afterwards. The default, and the only source for nba, euroleague, football, tennis and wta.
• ``backtest`` — a chronological out-of-sample holdout of the deployed model, graded after the fact. Published for ``champions`` only, because that module has no as-published record yet. It is a weaker claim than ``production`` and is labelled as such in the response's own ``source`` field.
• ``test`` (Phase 3.1) — the held-out test set of the currently deployed model. Not yet exposed.

**Read the response's ``source`` field, never the request.** A sport that has no record under the requested source answers 404 with an explanation; it never substitutes another regime's numbers.

## Path parameters

- `sport` 'nba' | 'euroleague' | 'football' | 'tennis' | 'wta' | 'champions', required

## Query parameters

- `source` 'production' | 'test' | 'backtest': Mutually exclusive performance source. Defaults to production for every sport — champions has no production record yet and answers 404 there, pointing at ?source=backtest. The default is deliberately NOT switched per sport: a default that silently changes regime is how a backtest ends up labelled production.
- `league` 'serie_a' | 'premier_league' | 'la_liga' | 'bundesliga' | 'ligue_1' | 'championship' | 'league_one' | 'league_two' | 'segunda_division' | 'ligue_2' | 'bundesliga_2' | 'serie_b'

## Headers

- `authorization` string, nullable

## Response `200`

Successful Response

- union
  - NBAPerformance
    - `sport` 'nba'
    - `source` 'production' | 'test', required
    - `sample_size` integer, required: Number of completed matches aggregated into this snapshot.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `winner_accuracy` number, nullable: Fraction of matches where the winner classifier picked the correct side.
    - `point_diff_mae` number, nullable: Mean absolute error of (predicted_point_diff − actual_point_diff).
    - `total_points_mae` number, nullable: Mean absolute error of (predicted_total_points − actual_total_points). Null when migration 007 columns not yet populated.
  - EuroLeaguePerformance
    - `sport` 'euroleague'
    - `source` 'production' | 'test', required
    - `sample_size` integer, required: Number of completed games aggregated into this snapshot.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `winner_accuracy` number, nullable: Fraction of games where the winner classifier picked the correct side.
    - `point_diff_mae` number, nullable: Mean absolute error of (predicted_point_diff − actual_point_diff).
    - `total_points_mae` number, nullable: Mean absolute error of (predicted_total_points − actual_total_points).
  - FootballPerformance
    - `sport` 'football'
    - `source` 'production' | 'test', required
    - `league` string, nullable: Customer-facing league code (e.g. 'serie_a') when the query narrowed to a single league; null for the cross-league aggregate.
    - `sample_size` integer, required: Number of completed matches aggregated into this snapshot.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `three_way_accuracy` number, nullable: Accuracy of the home/draw/away (1X2) classifier.
    - `over_under_2_5_accuracy` number, nullable: Accuracy of the over/under 2.5 goals classifier.
    - `btts_accuracy` number, nullable: Accuracy of the both-teams-to-score classifier.
  - TennisPerformance
    - `sport` 'tennis'
    - `source` 'production' | 'test', required
    - `sample_size` integer, required: Number of completed matches aggregated into this snapshot.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `winner_accuracy` number, nullable: Fraction of matches where the winner classifier picked the correct player.
    - `total_games_mae` number, nullable: Mean absolute error of the Poisson total-games regressor vs the observed game count.
  - WTAPerformance: WTA (women's tennis) — same headline metrics as ATP tennis.
    - `sport` 'wta'
    - `source` 'production' | 'test', required
    - `sample_size` integer, required: Number of completed matches aggregated into this snapshot.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `winner_accuracy` number, nullable: Fraction of matches where the winner classifier picked the correct player.
    - `total_games_mae` number, nullable: Mean absolute error of the Poisson total-games regressor vs the observed game count.
  - ChampionsPerformance: UEFA Champions League 1X2 match-model record. ``source`` is authoritative and, today, is always ``backtest``. Read the field, not the endpoint: these matches were graded AFTER they were played, by a model refit on seasons through 2021 under a frozen protocol. That is genuinely out-of-sample, and it is still not the same claim as "published before kick-off". No champions prediction existed in our database before these matches started, so ``?source=production`` answers 404 rather than relabelling this record.
    - `sport` 'champions'
    - `source` 'backtest' | 'production', required
    - `slice_name` string: Evaluated slice. 'ucl_tournament' = Champions League proper, qualifying rounds excluded.
    - `sample_size` integer, required: Number of completed matches graded.
    - `date_range` DateRange, required
      - `earliest` string, date, nullable: Earliest match date in the aggregated sample.
      - `latest` string, date, nullable: Latest match date in the aggregated sample.
    - `seasons` SeasonPerformance[]: Per-season breakdown. The pooled metrics above are the n-weighted recombination of these rows — never a second, independently stored total.
      - `season` string, required: UEFA seasonYear — the season ENDING year. '2026' is the 2025-26 campaign.
      - `n_matches` integer, required
      - `accuracy` number, nullable
      - `logloss` number, nullable
      - `brier` number, nullable
      - `rps` number, nullable
    - `three_way_accuracy` number, nullable: Share of matches whose highest 1X2 probability was the outcome.
    - `logloss` number, nullable: Mean -log p(outcome). Lower is better.
    - `brier` number, nullable: MULTICLASS sum-of-squares Brier over (home, draw, away) — the 0-2 scale, averaged over matches. NOT the 0-1 binary Brier the basketball endpoints report; do not plot them on one axis.
    - `rps` number, nullable: Ranked Probability Score over the ordered (home, draw, away) triple, the standard football 1X2 proper score. Lower is better.
    - `ece_home` number, nullable: Expected calibration error of P(home win) over 10 equal-width bins, pooled over the WHOLE sample. Read from the same calibration payload /v1/calibration/champions/champions_three_way_result serves — ECE is a bin-weighted absolute error and does not pool by averaging the per-season values, so the per-season ``ece_home`` figures are deliberately not recombined into this field.
    - `model_note` string, nullable: Provenance of the model vintage that produced these probabilities. Refits are manual in this module, so this is the only trace linking a published number to a reviewed model.
    - `coverage_note` string, nullable: Plain-language statement of what this record is and is not.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/predixsport/apis/predixsport-api.md) · [All operations](https://skmtc.dev/predixsport/apis/predixsport-api/llms.txt) · [OpenAPI document](https://skmtc.dev/predixsport/apis/predixsport-api/revisions/026afdfa7802?raw)
