{"openapi":"3.1.0","info":{"title":"predixsport-api","description":"Public REST API for Predixsport AI sports prediction models. Probabilistic predictions and ratings for NBA, EuroLeague, football, ATP and WTA tennis, and the UEFA Champions League, powered by calibrated neural network and Poisson/Negative-Binomial regression models. EuroLeague adds per-player PIR projections and full calibrated spread/total distributions.\n\n**Authentication**: pass `Authorization: Bearer psk_<...>` on every request. Mint a key at https://predixsport.com/api-access.\n\n**Rate limit**: free tier is **200 requests per calendar month** per user (shared across all your active keys). On exceeded quota the API returns `429 Too Many Requests` with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix ts) and `X-RateLimit-Reset-Date` (YYYY-MM-DD) headers. Need more? See plans at https://predixsport.com/pricing.","version":"0.2.0"},"paths":{"/v1/health":{"get":{"tags":["meta"],"summary":"Liveness probe (no auth)","description":"Always-on, unauthenticated endpoint returning the service status, running version and current UTC time. Used by the Docker healthcheck and Cloudflare's origin health probe.","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Health V1 Health Get"}}}}}}},"/v1/sports":{"get":{"tags":["catalog"],"summary":"List supported sports + their leagues","description":"Returns the canonical list of `sport` codes accepted by every other endpoint (`nba`, `euroleague`, `football`, `tennis` for ATP men's tennis, `wta` for WTA women's tennis, `champions` for the UEFA Champions League) plus, where applicable, the list of league codes that can be passed as `?league=` on football endpoints. Coverage is not uniform: `champions` exposes season simulations (`/v1/simulations/champions`), per-match 1X2 predictions (`/v1/predictions/champions/...`, league phase onward only — qualifying is never published), per-player track records (`/v1/players/champions/{player}/prediction-performance`), model performance (`/v1/performance/champions?source=backtest`) and calibration; `/v1/ratings` answers 404 for it. The set is stable; new entries are additive within v1.","operationId":"list_sports_v1_sports_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/models":{"get":{"tags":["catalog"],"summary":"List deployed prediction models","description":"Returns metadata (sport, task, target, classes if multiclass) for every deployed model. Use the model `id` to query its calibration via `/v1/calibration/{sport}/{model}`.","operationId":"list_models_v1_models_get","parameters":[{"name":"sport","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SportEnum"},{"type":"null"}],"description":"Filter to a single sport's models.","title":"Sport"},"description":"Filter to a single sport's models."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/nba/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming NBA matches with predictions","description":"Returns matches whose scheduled date falls in the inclusive window ``[today, today + days]``. The filter is a SQL ``BETWEEN`` against ``daily_predictions.prediction_date`` — equivalent to ``prediction_date >= today AND prediction_date <= today + days``. ``today`` is the server's current UTC date at request time. A match scheduled for today itself is included. Returns ``{matches: [], count: 0}`` (HTTP 200) when no rows fall in the window — typically because the season has ended or the inference pipeline has not produced predictions for upcoming dates yet. Use ``/v1/predictions/nba/search`` to query historical matches.","operationId":"nba_upcoming_v1_predictions_nba_upcoming_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/euroleague/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming EuroLeague matches with predictions","description":"Returns EuroLeague (European club basketball) matches whose scheduled date falls in the inclusive window ``[today, today + days]`` — a SQL ``BETWEEN`` against ``euroleague_predictions.prediction_date``. Returns ``{matches: [], count: 0}`` (HTTP 200) when no rows fall in the window — typically off-season (the EuroLeague season runs ~October–May). Use ``/v1/predictions/euroleague/search`` to query historical matches.","operationId":"euroleague_upcoming_v1_predictions_euroleague_upcoming_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroLeagueUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/football/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming football matches with predictions","description":"Returns matches whose scheduled date falls in the inclusive window ``[today, today + days]``. The filter is a SQL ``BETWEEN`` against ``football_predictions_<league>.prediction_date`` — equivalent to ``prediction_date >= today AND prediction_date <= today + days``. ``today`` is the server's current UTC date at request time. A match scheduled for today itself is included. Without ``league``, all 12 league tables (5 top flights + 7 second divisions) are queried and merged. Returns ``{matches: [], count: 0}`` (HTTP 200) when no rows fall in the window — typically off-season. Use ``/v1/predictions/football/search`` for historical queries.","operationId":"football_upcoming_v1_predictions_football_upcoming_get","parameters":[{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"title":"League"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FootballUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/tennis/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming tennis matches with predictions","description":"Returns every match from the most recent tennis-pipeline run. Unlike NBA / football, tennis ``date`` reflects the tournament's scheduled play-day rather than the actual calendar day a match is played, and isn't reliably updated when ATP reschedules a slot. Filtering on it produces false negatives, so this endpoint instead joins on ``MAX(current_date_time_search)`` — the timestamp of the latest pipeline run — and returns every row from it. The result is the same set of fixtures the public webapp shows on its tennis hub. The ``days`` query parameter is accepted for API backward-compatibility but is NOT applied: a single run already covers the active tournament's near future. Use ``/v1/predictions/tennis/search`` for historical queries.","operationId":"tennis_upcoming_v1_predictions_tennis_upcoming_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/wta/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming WTA (women's tennis) matches with predictions","description":"Returns every WTA (women's tennis) match from the most recent WTA-pipeline run. Same semantics as the ATP ``tennis`` sport: the ``date`` field reflects the tournament's scheduled play-day rather than the actual calendar day a match is played, and isn't reliably updated when the tour reschedules a slot. Filtering on it produces false negatives, so this endpoint instead joins on ``MAX(current_date_time_search)`` — the timestamp of the latest pipeline run — and returns every row from it. The ``days`` query parameter is accepted for API backward-compatibility but is NOT applied: a single run already covers the active tournament's near future. Every WTA match — Grand Slams included — is best-of-three sets. Use ``/v1/predictions/wta/search`` for historical queries.","operationId":"wta_upcoming_v1_predictions_wta_upcoming_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WTAUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/champions/upcoming":{"get":{"tags":["predictions"],"summary":"Upcoming Champions League matches with predictions","description":"Upcoming UEFA Champions League matches inside the window ``[today, today + days]``, with 1X2 probabilities. **Scope: the tournament only** — the Swiss league phase, the knockout play-off and the round of 16 onwards. The July/August qualifying rounds are ingested and drive the European Elo ladder, but they are not predicted: the model's validated slice is the tournament, and a qualifying tie is dominated by clubs the ladder has seen a handful of times. **An empty list is the normal answer between late May and the league-phase draw in late August** — there is no eligible fixture in existence then, so render it as \"no fixtures yet\", not as an outage. Only 1X2 is published: totals, both-teams-score, Asian handicaps and exact score were all measured on this model's frozen protocol and held back. `expected_goals_*` are the model's own Poisson rates, published as context and never as a totals line.","operationId":"champions_upcoming_v1_predictions_champions_upcoming_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60. The pipeline publishes on a 7-day horizon, so a larger value widens the read, not the pipeline.","default":7,"title":"Days"},"description":"Look-ahead horizon in days. Defines the upper bound of the inclusive window ``[today, today + days]``. Default 7, max 60. The pipeline publishes on a 7-day horizon, so a larger value widens the read, not the pipeline."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChampionsUpcomingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/nba/search":{"get":{"tags":["predictions"],"summary":"Search historical NBA matches","description":"Filters operate as AND. Team filter performs a case-insensitive partial match on home_team / away_team. Use this to discover match_id values for /v1/predictions/nba/{match_id}.","operationId":"nba_search_v1_predictions_nba_search_get","parameters":[{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Team"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":1900},{"type":"null"}],"title":"Season"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBASearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/euroleague/search":{"get":{"tags":["predictions"],"summary":"Search historical EuroLeague matches","description":"Filters operate as AND. Team filter performs a case-insensitive partial match on home_team / away_team. Use this to discover match_id values for /v1/predictions/euroleague/{match_id}.","operationId":"euroleague_search_v1_predictions_euroleague_search_get","parameters":[{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Team"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[A-Za-z0-9_]{4,10}$"},{"type":"null"}],"description":"EuroLeague season code, e.g. \"E2025\".","title":"Season"},"description":"EuroLeague season code, e.g. \"E2025\"."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroLeagueSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/football/search":{"get":{"tags":["predictions"],"summary":"Search historical football matches","description":"Filters operate as AND. Team filter performs a case-insensitive partial match on home_team / away_team across the league table(s). Without ``league``, all 12 tables (5 top flights + 7 second divisions) are queried and merged. Use this to discover ``match_id`` values for ``GET /v1/predictions/football/{match_id}``.","operationId":"football_search_v1_predictions_football_search_get","parameters":[{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Team"}},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"title":"League"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[0-9_]{4,9}$"},{"type":"null"}],"description":"Season string as stored, e.g. \"2025_2026\".","title":"Season"},"description":"Season string as stored, e.g. \"2025_2026\"."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FootballSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/tennis/search":{"get":{"tags":["predictions"],"summary":"Search historical tennis matches","description":"Player names are alias-resolved (e.g. ``djokovic`` → ``Novak Djokovic``) before matching. Passing two ``player`` parameters performs a bidirectional match — the row is returned regardless of which player was recorded as ``player_one`` vs ``player_two`` at ingestion.","operationId":"tennis_search_v1_predictions_tennis_search_get","parameters":[{"name":"player","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"maxItems":80,"title":"Player"}},{"name":"tournament","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Tournament"}},{"name":"surface","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[A-Za-z]{1,20}$"},{"type":"null"}],"description":"Filter on surface, e.g. clay, hard, grass.","title":"Surface"},"description":"Filter on surface, e.g. clay, hard, grass."},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[0-9_]{4,9}$"},{"type":"null"}],"title":"Season"}},{"name":"phase","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Phase"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/wta/search":{"get":{"tags":["predictions"],"summary":"Search historical WTA (women's tennis) matches","description":"Same semantics as the ATP ``tennis`` search. Player names are alias-resolved before matching. Passing two ``player`` parameters performs a bidirectional match — the row is returned regardless of which player was recorded as ``player_one`` vs ``player_two`` at data ingestion.","operationId":"wta_search_v1_predictions_wta_search_get","parameters":[{"name":"player","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"maxItems":80,"title":"Player"}},{"name":"tournament","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Tournament"}},{"name":"surface","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[A-Za-z]{1,20}$"},{"type":"null"}],"description":"Filter on surface, e.g. clay, hard, grass.","title":"Surface"},"description":"Filter on surface, e.g. clay, hard, grass."},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[0-9_]{4,9}$"},{"type":"null"}],"title":"Season"}},{"name":"phase","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":20,"minimum":1},{"type":"null"}],"title":"Phase"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WTASearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/champions/search":{"get":{"tags":["predictions"],"summary":"Search the published Champions League record","description":"Searches every Champions League match this service has published a prediction for, played or upcoming. `team` is a substring match on the club name AS UEFA RECORDS IT, which is the native spelling: `Bayern München`, not `Bayern Munich`. Partial terms work and are the safe way to search — `bayern`, `zvezda`, `Žalgiris`. `season` is the UEFA seasonYear — the season ENDING year, so `2027` means the 2026-27 campaign. Qualifying-round ties never appear: they are outside the model's validated scope and are never published. Rows carry `actual_result` once the match has been played.","operationId":"champions_search_v1_predictions_champions_search_get","parameters":[{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Team"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":8,"pattern":"^[0-9]{4}$"},{"type":"null"}],"description":"UEFA seasonYear (season ENDING year), e.g. \"2027\".","title":"Season"},"description":"UEFA seasonYear (season ENDING year), e.g. \"2027\"."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChampionsSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/champions/player-forecasts":{"get":{"tags":["predictions"],"summary":"Champions League player projection distributions after official team sheets","description":"Returns Predixsport's own goal, shot, shot-on-target, recovered-ball and goalkeeper-save count distributions after the official team sheet is published (T-75). It contains no official fantasy prices, ownership, injury data or observed per-match player-stat table. Without match_id, only the current live window is returned. An empty list before team sheets is the expected state. Assist is a mean only because its full distribution has not passed our publication bar.","operationId":"champions_player_forecasts_v1_predictions_champions_player_forecasts_get","parameters":[{"name":"match_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$"},{"type":"null"}],"title":"Match Id"}},{"name":"player","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Player"}},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Team"}},{"name":"starters_only","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Starters Only"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":250,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChampionsPlayerForecastResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/nba/{match_id}":{"get":{"tags":["predictions"],"summary":"Full NBA match payload (game-level + per-player projections)","description":"Returns the full payload for a single NBA match identified by ``match_id`` (the `file_name` column on `daily_predictions`, discoverable via the `search` endpoint). Includes winner probabilities on `home_team` / `away_team`, dedicated `predicted_point_diff` (spread regressor) and `predicted_total_points` (total regressor), plus a `players` array — one entry per projected player carrying expected points/rebounds/assists/3PM and the full count PMF for each. Probabilities across families come from independent models — small numeric mismatches are by design. Returns **404** if the `match_id` is unknown. Use `GET /v1/predictions/nba/{match_id}/players` if you only need the player layer.","operationId":"nba_match_detail_v1_predictions_nba__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/euroleague/{match_id}":{"get":{"tags":["predictions"],"summary":"Full EuroLeague match payload (game-level + per-player projections incl. PIR)","description":"Returns the full payload for a single EuroLeague match identified by ``match_id`` (the ``file_name`` column on ``euroleague_predictions``, e.g. ``E2025_61`` — discoverable via the ``search`` endpoint). Includes winner probabilities, ``predicted_point_diff`` (spread) and ``predicted_total_points``, the full calibrated ``distributions`` (spread + total PMFs), and a ``players`` array carrying expected points/rebounds/assists/3PM and **PIR** (the EuroLeague signature stat) with the full PMF for each. The sub-models are independent — small cross-metric mismatches are by design. Returns **404** if the ``match_id`` is unknown. Use ``GET /v1/predictions/euroleague/{match_id}/players`` for the player layer only.","operationId":"euroleague_match_detail_v1_predictions_euroleague__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroLeagueMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/football/{match_id}":{"get":{"tags":["predictions"],"summary":"Full football match payload (probabilities + distributions/CDFs)","description":"Returns the full payload for a single football match. Includes the 1X2 (`home_win` / `draw` / `away_win`), over/under 2.5 and BTTS probabilities, expected values for corners / shots / goal spread, and the full count PMF (`distributions`) and CDF (`cdfs`) for each. Three independent classifiers feed the probability fields — cross-family mismatches on the same match are expected. Without the optional `league` hint the service probes all 12 league tables (5 top flights + 7 second divisions) in turn; pass `?league=...` to skip the probe and save a roundtrip when you know the league. Returns **404** when no row matches in any league table.","operationId":"football_match_detail_v1_predictions_football__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"description":"Optional league hint to skip the cross-league probe.","title":"League"},"description":"Optional league hint to skip the cross-league probe."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FootballMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/tennis/{match_id}":{"get":{"tags":["predictions"],"summary":"Full tennis match payload (winner, total games, spread, aces, double faults, tiebreak, exact score)","description":"Returns the full payload for a single tennis match. Player order (`player_one` / `player_two`) is **as observed** at data ingestion — NEVER winner-first canonicalised. All player-relative numbers (spread, exact_score classes, each player's serve projections) are aligned to that ordering. Aces and double faults are served at **both levels**: `expected_aces` / `expected_double_faults` (plus their entries in `distributions` and `cdfs`) stay the **match totals**, and `player_one` / `player_two` each carry that player's own `expected_aces`, `expected_double_faults` and the matching `*_distribution` / `*_cdf`. The total is the sum of the two per-player projections, so it means exactly what it always meant. The per-player fields are null for matches predicted before the August 2026 rollout. Once a match is played, each player also carries `actual_aces` and `actual_double_faults`, the counts that player really served, matched to the player by NAME so they can be scored directly against that player's projection and distribution. The seven sub-models (winner classifier, total-games and spread Poisson regressors, per-player aces and double-faults negative-binomial regressors, tiebreak classifier, exact_score multiclass) are independent — small cross-metric mismatches are by design. `tournament.date` is the tournament START day, not the specific match day. Returns **404** if the `match_id` is unknown.","operationId":"tennis_match_detail_v1_predictions_tennis__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/wta/{match_id}":{"get":{"tags":["predictions"],"summary":"Full WTA (women's tennis) match payload (winner, total games, spread, aces, double faults, tiebreak, exact score)","description":"Returns the full payload for a single WTA match — same contract as the ATP ``tennis`` sport. Player order (`player_one` / `player_two`) is **as observed** at data ingestion — NEVER winner-first canonicalised. All player-relative numbers (spread, exact_score classes and serve projections) are aligned to that ordering. Aces and double faults are available both per player and as derived match totals; the totals retain their existing API meaning. A played match adds `actual_aces` and `actual_double_faults` to each player, matched by NAME, so a projection can be scored against what that player really served. The seven sub-models (winner classifier, total/spread regressors, per-player aces/double-faults regressors, tiebreak classifier and exact-score multiclass) are independent — small cross-metric mismatches are by design. Every WTA match is best-of-three sets (Grand Slams included), so the exact-score distribution carries only the four bo3 classes. `tournament.date` is the tournament START day, not the specific match day. Returns **404** if the `match_id` is unknown.","operationId":"wta_match_detail_v1_predictions_wta__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WTAMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/champions/{match_id}":{"get":{"tags":["predictions"],"summary":"Full Champions League match payload (1X2, goal rates, key factors)","description":"Full payload for one Champions League match: the 1X2 triple, the model's Poisson goal rates, the pre-match European Elo of both clubs, and the concept-level key factors behind P(home win) in probability points. Once the match is played, `actual` carries the 90-minute result and whether the published pick was right (one match is not evidence about a model — use `/v1/performance/champions` for that). `key_factors` may be `null`: the explanation is advisory data on top of the prediction and never blocks one. Only 1X2 is published; the goals family was measured on this model and held back. Returns **404** if the `match_id` is unknown — including for a qualifying tie, which is deliberately never predicted.","operationId":"champions_match_detail_v1_predictions_champions__match_id__get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChampionsMatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/nba/{match_id}/players":{"get":{"tags":["predictions"],"summary":"NBA per-player projections for a single match","description":"Returns the same ``players`` array embedded in the NBA match-detail payload, exposed as a standalone endpoint for callers that only need the player layer (e.g. fantasy-app builders).\n\nROW SET: the projected ROSTER of each team, around 19 names a side, not the expected starting five. Twelve stats are projected for each of them (points, rebounds, assists, three-pointers made, steals, blocks, turnovers, field goals made and attempted, free throws made and attempted, NBA Salary Cap points), each with an expectation and a full discrete distribution. Every field beyond the original four heads is nullable and simply absent for a match predicted before the full-roster rollout.\n\nHOW TO READ THEM: all twelve models beat a naive rolling average on the distribution. Five (points, rebounds, field goals made, field goals attempted, NBA Salary Cap points) also beat it on single-number error and are safe as point estimates. For the other seven, prefer the distribution or a quantile band over the expectation alone.\n\nAVAILABILITY: ``started`` and ``projection_mode`` describe the published lineup of the day and nothing else. There is no probability that a player features and no injury data anywhere in this response, by design.\n\n``preset`` adds a ``fantasy`` object per player with that scoring system's projected score.","operationId":"nba_match_players_v1_predictions_nba__match_id__players_get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"preset","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyPresetEnum"},{"type":"null"}],"description":"Fantasy scoring system to score each player under. ``espn_points`` (PTS 1, 3PM 1, FGM 2, FGA -1, FTM 1, FTA -1, REB 1, AST 2, STL 4, BLK 4, TOV -2), ``sleeper_points`` (PTS 1, REB 1.2, AST 1.5, STL 3, BLK 3, TOV -1), ``nba_salary_cap`` (PTS 1, REB 1, AST 2, BLK 3, STL 3) or ``yahoo_9cat``, which is a per-category league and therefore returns the nine category values instead of a single score. ``nba_salary_cap`` is scored by a model trained on that total itself, so it carries quantiles and a ``formula_expectation`` cross-check beside the served ``expectation``. Every other points preset is the formula applied to the component projections, which is exact for the average, and carries no quantiles. ``expectation_source`` says which reading you got. Omit the parameter and the response is unchanged.","title":"Preset"},"description":"Fantasy scoring system to score each player under. ``espn_points`` (PTS 1, 3PM 1, FGM 2, FGA -1, FTM 1, FTA -1, REB 1, AST 2, STL 4, BLK 4, TOV -2), ``sleeper_points`` (PTS 1, REB 1.2, AST 1.5, STL 3, BLK 3, TOV -1), ``nba_salary_cap`` (PTS 1, REB 1, AST 2, BLK 3, STL 3) or ``yahoo_9cat``, which is a per-category league and therefore returns the nine category values instead of a single score. ``nba_salary_cap`` is scored by a model trained on that total itself, so it carries quantiles and a ``formula_expectation`` cross-check beside the served ``expectation``. Every other points preset is the formula applied to the component projections, which is exact for the average, and carries no quantiles. ``expectation_source`` says which reading you got. Omit the parameter and the response is unchanged."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NBAPlayerProjection"},"title":"Response Nba Match Players V1 Predictions Nba  Match Id  Players Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/nba/players/week":{"get":{"tags":["predictions"],"summary":"NBA per-player projections for games later in the week","description":"Lineup-free NBA player projections for scheduled games that have not been played, the look-ahead companion to the per-match endpoint. The same models run without a published starting five, so ``projection_mode`` is always ``calendar`` and ``started`` is always null.\n\nAN EMPTY LIST IS THE NORMAL STATE, not an outage. NBA predictions are gated on the expected starting five, so the per-match surface exists only on game day, and this one exists only once the rest-of-week mode has run for the current batch.\n\nTWO VIEWS OF THE SAME BATCH. ``players`` is one row per player per scheduled game. ``totals`` is one row per player for the WHOLE window, best first: games scheduled, the days they fall on, the projected points over the week and, with a ``preset``, that league's projected score for the week and the same number per game. ``totals`` is what a week planner reads, and it is built over every row that matches the filters rather than over the ``limit`` page of ``players``. Pass ``include_rows=false`` to get it on its own.\n\nTHE WEEK IS AN EXPECTATION AND NOTHING ELSE. A weekly number is the sum of the per-game expectations, exact for the mean by linearity, but a sum of dependent counts is not the convolution of the marginals, so no band, no distribution and no probability is published for it. There is no availability field here either, by design: days ahead of a game no lineup exists.\n\nUse it to plan which players to pick up for the days ahead, then call the per-match endpoint on game day for the projection that knows the published five.","operationId":"nba_players_week_v1_predictions_nba_players_week_get","parameters":[{"name":"player","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"description":"Exact stored player name, e.g. 'Nikola Jokic'.","title":"Player"},"description":"Exact stored player name, e.g. 'Nikola Jokic'."},{"name":"names","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"description":"A whole roster at once: player names separated by commas, semicolons or newlines. Matching ignores case and accents (doncic finds Luka Dončić) and accepts a surname on its own or a surname with a first-name prefix (L. James). A name that matches nobody, or more than one player, is NOT guessed at: it comes back in ``unresolved_names`` while the rest are resolved. Use it instead of one request per player.","title":"Names"},"description":"A whole roster at once: player names separated by commas, semicolons or newlines. Matching ignores case and accents (doncic finds Luka Dončić) and accepts a surname on its own or a surname with a first-name prefix (L. James). A name that matches nobody, or more than one player, is NOT guessed at: it comes back in ``unresolved_names`` while the rest are resolved. Use it instead of one request per player."},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"description":"Exact stored franchise name, e.g. 'Denver Nuggets'.","title":"Team"},"description":"Exact stored franchise name, e.g. 'Denver Nuggets'."},{"name":"court","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2,"pattern":"^[BbFf][Cc]$"},{"type":"null"}],"description":"Half of the floor: ``BC`` backcourt (guards) or ``FC`` frontcourt (forwards and centres). Formats that require a backcourt / frontcourt split filter on it.","title":"Court"},"description":"Half of the floor: ``BC`` backcourt (guards) or ``FC`` frontcourt (forwards and centres). Formats that require a backcourt / frontcourt split filter on it."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum rows in ``players`` AND maximum players in ``totals``, applied to each independently.","default":200,"title":"Limit"},"description":"Maximum rows in ``players`` AND maximum players in ``totals``, applied to each independently."},{"name":"include_rows","in":"query","required":false,"schema":{"type":"boolean","description":"Set false to omit the per-game ``players`` array and receive the per-player ``totals`` alone, which is the smaller answer and the one a week planner needs.","default":true,"title":"Include Rows"},"description":"Set false to omit the per-game ``players`` array and receive the per-player ``totals`` alone, which is the smaller answer and the one a week planner needs."},{"name":"preset","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyPresetEnum"},{"type":"null"}],"description":"Fantasy scoring system to score each row under, exactly as on the per-match endpoint. It also fills ``expectation`` in ``totals``: without it a total carries the games, the days and the projected points only.","title":"Preset"},"description":"Fantasy scoring system to score each row under, exactly as on the per-match endpoint. It also fills ``expectation`` in ``totals``: without it a total carries the games, the days and the projected points only."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAPlayerWeekResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/predictions/euroleague/{match_id}/players":{"get":{"tags":["predictions"],"summary":"EuroLeague per-player projections (incl. PIR) for a single match","description":"Returns the same ``players`` array embedded in the EuroLeague match-detail payload — expected points/rebounds/assists/3PM/PIR plus the full PMF for each — exposed standalone for player-only callers (e.g. fantasy / DFS tools).","operationId":"euroleague_match_players_v1_predictions_euroleague__match_id__players_get","parameters":[{"name":"match_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":120,"pattern":"^[A-Za-z0-9_\\-\\.]{1,120}$","title":"Match Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EuroLeaguePlayerProjection"},"title":"Response Euroleague Match Players V1 Predictions Euroleague  Match Id  Players Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calibration/{sport}/{model_id}":{"get":{"tags":["calibration"],"summary":"Calibration data for a deployed model","description":"Returns the latest ``_deploy.json`` calibration payload published by the relevant AI pipeline. Use ``GET /v1/models`` to list valid (sport, model_id) pairs. The shape under ``data`` is sport- and task-specific — see the source data on the methodology page.","operationId":"calibration_for_model_v1_calibration__sport___model_id__get","parameters":[{"name":"sport","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SportEnum"}},{"name":"model_id","in":"path","required":true,"schema":{"type":"string","minLength":3,"maxLength":80,"pattern":"^[a-z0-9_]+$","title":"Model Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalibrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/performance/{sport}":{"get":{"tags":["performance"],"summary":"Aggregate model performance for a sport","description":"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:\n\n• ``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.\n• ``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.\n• ``test`` (Phase 3.1) — the held-out test set of the currently deployed model. Not yet exposed.\n\n**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.","operationId":"performance_for_sport_v1_performance__sport__get","parameters":[{"name":"sport","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SportEnum"}},{"name":"source","in":"query","required":false,"schema":{"enum":["production","test","backtest"],"type":"string","description":"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.","default":"production","title":"Source"},"description":"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."},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"description":"Football only — narrow to one league. Omit for cross-league aggregate.","title":"League"},"description":"Football only — narrow to one league. Omit for cross-league aggregate."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/NBAPerformance"},{"$ref":"#/components/schemas/EuroLeaguePerformance"},{"$ref":"#/components/schemas/FootballPerformance"},{"$ref":"#/components/schemas/TennisPerformance"},{"$ref":"#/components/schemas/WTAPerformance"},{"$ref":"#/components/schemas/ChampionsPerformance"}],"title":"Response Performance For Sport V1 Performance  Sport  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/players/nba/{player}/prediction-performance":{"get":{"tags":["players"],"summary":"How well the NBA projection models track one player","description":"Per-player reconciliation of our pre-game projections (points, rebounds, assists, threes) against the realized box score, for games the player STARTED — projections assume the starting five, so a late scratch is excluded rather than counted as a miss. `player` accepts the display name (\"LeBron James\") or its slug (\"lebron-james\"). Returns per-stat accuracy (model MAE vs a naive rolling-average baseline, CRPS-based distributional skill), a predictability score (points + rebounds skill), and the most recent started games with expected vs actual values and the pre-game p10-p90 band. `source` distinguishes the full-season holdout backtest (2025-26) from live next-day reconciliation (from the 2026-27 season). Typical use: decide whether to trust today's projection for a fantasy start/sit call by checking how the model has tracked that player recently.","operationId":"nba_player_prediction_performance_v1_players_nba__player__prediction_performance_get","parameters":[{"name":"player","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Player"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":2000},{"type":"null"}],"description":"NBA season end-year (2026 = 2025-26)","title":"Season"},"description":"NBA season end-year (2026 = 2025-26)"},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent started games to return","default":10,"title":"Last"},"description":"How many recent started games to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAPlayerPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/players/euroleague/{player}/prediction-performance":{"get":{"tags":["players"],"summary":"How well the Euroleague projection models track one player","description":"Per-player reconciliation of our pre-game projections (points, rebounds, assists, threes and PIR) against the realized box score, for games the player STARTED — projections assume the starting five, so a late scratch is excluded rather than counted as a miss. `player` accepts the display name (\"Kevin Punter\") or its slug (\"kevin-punter\"). Returns per-stat accuracy (model MAE vs a naive rolling-average baseline, CRPS-based distributional skill), a predictability score (points + rebounds + PIR skill), and the most recent started games with expected vs actual values and the pre-game p10-p90 band. PIR is the Euroleague's own performance index rating and the only projected stat here that can be negative. `source` distinguishes the out-of-sample season replay (2025-26) from live per-matchday reconciliation (from the 2026-27 season). Season keys are the Euroleague codes, e.g. `E2025` for 2025-26.","operationId":"euroleague_player_prediction_performance_v1_players_euroleague__player__prediction_performance_get","parameters":[{"name":"player","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Player"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^E(19|20)\\d{2}$"},{"type":"null"}],"description":"Euroleague season code, e.g. E2025 for 2025-26","title":"Season"},"description":"Euroleague season code, e.g. E2025 for 2025-26"},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent started games to return","default":10,"title":"Last"},"description":"How many recent started games to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroLeaguePlayerPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/players/champions/{player}/prediction-performance":{"get":{"tags":["players"],"summary":"How well the Champions League player models track one player","description":"Per-player track record of our UEFA Champions League player projections, for matches the player STARTED. `player` accepts the display name (\"Vinicius Junior\") or its slug (\"vinicius-junior\"). Returns CRPS skill against a naive baseline (role rate x expected minutes) on shots and shots on target, the anytime-scorer Brier skill as a secondary, goalkeeper saves for keepers, and one summary per covered season.\n\nThis response is AGGREGATE ONLY: there is no per-match array here, unlike the NBA and Euroleague player endpoints. Champions League player surfaces publish season-level accuracy of our own forecasts and never a per-match statistics feed.\n\nSeasons are the UEFA season ENDING year as a string: `2026` is the 2025-26 campaign. `source` distinguishes the frozen out-of-sample holdout (2022-2026) from live reconciliation (from 2026-27). `mode` reports the serving mode the record was scored in: `t75` means the confirmed team sheet was out. Coverage starts at 8 started appearances in a season, a full league phase, below which the estimate is noise.","operationId":"champions_player_prediction_performance_v1_players_champions__player__prediction_performance_get","parameters":[{"name":"player","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Player"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"description":"UEFA season ending year, e.g. 2026 for the 2025-26 campaign","title":"Season"},"description":"UEFA season ending year, e.g. 2026 for the 2025-26 campaign"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChampionsPlayerPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/players/tennis/{player}/prediction-performance":{"get":{"tags":["players"],"summary":"Season script report for one ATP player (upsets vs expected)","description":"Season script report for one player: how often our model's favorite actually lost in their matches (upsets) versus how often the model itself expected it for that exact schedule (the sum of the priced favorite-loses probabilities). `player` accepts the as-observed display name (\"Jannik Sinner\") or its slug (\"jannik-sinner\"). Each season row carries actual vs expected upsets, the difference with a 95% interval, and a tier: on_script (fewer upsets than priced, shown on the site as \"Straight sets\"), in_line (\"On serve\") or chaos (\"Wild card\"). Verdicts are SEASON-level by design — being hard to predict does not persist year over year, so there is no career score. `source` distinguishes the as-published daily record (recent seasons) from a strict out-of-sample backtest of the current model (earlier seasons); one source per season, never mixed. Use `/tennis/...` for the ATP tour and `/wta/...` for the WTA tour.","operationId":"tennis_player_prediction_performance_v1_players_tennis__player__prediction_performance_get","parameters":[{"name":"player","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Player"}},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent matches to return","default":10,"title":"Last"},"description":"How many recent matches to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisPlayerPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/players/wta/{player}/prediction-performance":{"get":{"tags":["players"],"summary":"Season script report for one WTA player (upsets vs expected)","description":"Season script report for one player: how often our model's favorite actually lost in their matches (upsets) versus how often the model itself expected it for that exact schedule (the sum of the priced favorite-loses probabilities). `player` accepts the as-observed display name (\"Jannik Sinner\") or its slug (\"jannik-sinner\"). Each season row carries actual vs expected upsets, the difference with a 95% interval, and a tier: on_script (fewer upsets than priced, shown on the site as \"Straight sets\"), in_line (\"On serve\") or chaos (\"Wild card\"). Verdicts are SEASON-level by design — being hard to predict does not persist year over year, so there is no career score. `source` distinguishes the as-published daily record (recent seasons) from a strict out-of-sample backtest of the current model (earlier seasons); one source per season, never mixed. Use `/tennis/...` for the ATP tour and `/wta/...` for the WTA tour.","operationId":"wta_player_prediction_performance_v1_players_wta__player__prediction_performance_get","parameters":[{"name":"player","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Player"}},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent matches to return","default":10,"title":"Last"},"description":"How many recent matches to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisPlayerPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/clubs/football/{club}/prediction-performance":{"get":{"tags":["clubs"],"summary":"How well the football models track one club","description":"Per-club reconciliation of our as-published match predictions against realized results, across all 12 covered leagues. `club` accepts the stored short name (\"Man United\"), the display name (\"Manchester United\") or the slug (\"manchester-united\"). Returns the club's predictability (multiclass Brier score of the 1X2 probabilities vs the league average, with an 'on_script'/'in_line'/'chaos' tier), per-market accuracy (match result, under/over 2.5, both-teams-to-score) next to the league baseline, and the most recent reconciled matches with the pre-match probabilities. `source` distinguishes the frozen as-published record ('production') from a league's first covered season reconstructed at launch ('backtest'). `season_is_current` is False when an earlier season is served — explicitly requested, or because the current one has no or too few reconciled matches yet (early season). Per-club accuracies and the tier need 15 reconciled matches and are null below that. Typical use: judge how much to trust today's prediction for a club by checking how the model has tracked it this season.","operationId":"football_club_prediction_performance_v1_clubs_football__club__prediction_performance_get","parameters":[{"name":"club","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Club"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}_20\\d{2}$"},{"type":"null"}],"description":"Season key, e.g. 2025_2026. Omitted = the club's latest covered season.","title":"Season"},"description":"Season key, e.g. 2025_2026. Omitted = the club's latest covered season."},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent reconciled matches to return","default":10,"title":"Last"},"description":"How many recent reconciled matches to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FootballClubPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/euroleague/me":{"get":{"tags":["fantasy"],"summary":"Get my saved Euroleague Fantasy roster and latest advice","description":"Returns the private roster saved under the same Predixsport account, its current/final Round state and the latest still-applicable immutable recommendation. The owner is derived exclusively from the bearer token; this endpoint does not accept a user id. The roster may be continued through Web, REST or MCP under the same account.","operationId":"my_euroleague_fantasy_state_v1_fantasy_euroleague_me_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^E20\\d{2}$"},{"type":"null"}],"description":"Euroleague season key, e.g. E2026. Omitted = latest saved.","title":"Season"},"description":"Euroleague season key, e.g. E2026. Omitted = latest saved."},{"name":"board_limit","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":1,"description":"Maximum projected players in the current Round summary.","default":12,"title":"Board Limit"},"description":"Maximum projected players in the current Round summary."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/euroleague/me/roster":{"put":{"tags":["fantasy"],"summary":"Save my current Euroleague Fantasy roster","operationId":"save_my_euroleague_fantasy_roster_v1_fantasy_euroleague_me_roster_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyRosterWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasySavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/euroleague/me/optimize":{"post":{"tags":["fantasy"],"summary":"Optimize my Euroleague Fantasy Turn Card","description":"Saves the submitted current roster and appends advice without applying it. It never accesses or changes the official fantasy game.","operationId":"optimize_my_euroleague_fantasy_roster_v1_fantasy_euroleague_me_optimize_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyOptimizeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/euroleague/me/transfers":{"post":{"tags":["fantasy"],"summary":"Plan my Euroleague Fantasy transfers before a Round","description":"Saves the submitted current roster and appends an immutable between-Round transfer plan without applying it. The visible plan is the form rule (form_blend_v1) priced from the frozen market snapshot of the Round: it shows legal incoming and outgoing moves, cash before and after, and the reason for each move. Sale value is the current market price. It never accesses or changes the official fantasy game.","operationId":"plan_my_euroleague_fantasy_transfers_v1_fantasy_euroleague_me_transfers_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyTransfersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/euroleague/me/recommendations/{snapshot_id}/apply":{"post":{"tags":["fantasy"],"summary":"Apply saved Euroleague advice to my Predixsport roster","description":"Explicitly updates only the private roster saved at Predixsport. It never submits changes to the official Euroleague Fantasy game.","operationId":"apply_my_euroleague_fantasy_recommendation_v1_fantasy_euroleague_me_recommendations__snapshot_id__apply_post","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"integer","title":"Snapshot Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasyApplyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroleagueFantasySavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/tennis/me":{"get":{"tags":["fantasy"],"summary":"Get my ATP Fantasy roster, current board and latest advice","operationId":"my_tennis_fantasy_state_v1_fantasy_tennis_me_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"title":"Season"}},{"name":"board_limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":25,"minimum":1},{"type":"null"}],"description":"Optional projection-board limit. The response reports total and omitted player counts when a bounded summary is requested.","title":"Board Limit"},"description":"Optional projection-board limit. The response reports total and omitted player counts when a bounded summary is requested."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasyStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/tennis/me/roster":{"put":{"tags":["fantasy"],"summary":"Save my current ATP Fantasy roster","operationId":"save_my_tennis_fantasy_roster_v1_fantasy_tennis_me_roster_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasyRosterWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasySavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/tennis/me/optimize":{"post":{"tags":["fantasy"],"summary":"Optimize my ATP Fantasy roster for the open tournament week","operationId":"optimize_my_tennis_fantasy_roster_v1_fantasy_tennis_me_optimize_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasyRosterWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasyRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/tennis/me/recommendations/{snapshot_id}/apply":{"post":{"tags":["fantasy"],"summary":"Apply a saved ATP Fantasy recommendation after confirming prices","operationId":"apply_my_tennis_fantasy_recommendation_v1_fantasy_tennis_me_recommendations__snapshot_id__apply_post","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"integer","title":"Snapshot Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasyApplyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TennisFantasySavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me":{"get":{"tags":["fantasy"],"summary":"Get my saved NBA Fantasy roster, tonight's five and the week ahead","description":"Returns the private squad saved under the same Predixsport account and everything computed from it. A squad carries a `mode`: 'salary_cap' is the official NBA game and 'season_long' an ESPN-like points league. A season-long squad also returns a `league` node with the contract it is played under (active slots, per-player platform eligibility, matchup end), the days a plan can see, and the latest saved tonight lineup and add/drop plan under `league.snapshots`, each marked `stale` when the squad or the projections have moved since. A stale plan is history: run the matching POST again rather than acting on it. The board below is the official Salary Cap card and is computed on every read: the highest projected legal Gameday five out of the players you hold, the bench order behind it, every rostered player's week with the thinnest names called out, and the Captain call. The Captain call is a stated RULE, not a model pick (policy captain_form_daily_v1): it backs your best realised-form player OF THOSE WITH A GAME LEFT in the Gameweek, on the first day he is in his team's published five, and it forces the choice on the last day your squad can still spend the chip. The board says which Gameweek it worked in: 'official' is a hand-entered calendar of the game's own Gameweeks, 'iso_week_approximation' is the ISO week of the slate day and is not an official Gameweek. The owner is derived exclusively from the bearer token. This endpoint does not accept a user id. The same roster is edited in the web workspace and through the MCP tools. NBA projections exist on game day only, so an empty card with a populated roster is the normal state outside a slate, not an outage. A weekly number is a sum of per-game expectations and carries no distribution and no availability. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"my_nba_fantasy_state_v1_fantasy_nba_me_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"description":"NBA season, e.g. 2027. Omitted = the current season.","title":"Season"},"description":"NBA season, e.g. 2027. Omitted = the current season."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/leaders":{"get":{"tags":["fantasy"],"summary":"Get the league's best NBA Fantasy players for the rest of the Gameweek","description":"League-wide ranking for the NBA Salary Cap game: the players with the most projected fantasy points over the games they have LEFT in the current Gameweek, backcourt and frontcourt ranked apart. A player with four games left can outrank a better one with two, which is the point of a Gameweek view. The window runs from today (US Eastern) to the end of the Gameweek, read from the official calendar when we hold it (`gameweek.source`). A weekly number is a sum of per-game expectations and carries no distribution and no availability. Early in the season some games are projected by an early-season prior instead of the model's own rows: each player says so in `prior` and `basis` says so for the whole read. An empty ranking is the normal state when no batch covers the window, not an outage. This read touches no owner state and needs no saved squad. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"nba_fantasy_leaders_v1_fantasy_nba_leaders_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"description":"NBA season, e.g. 2027. Omitted = the current season.","title":"Season"},"description":"NBA season, e.g. 2027. Omitted = the current season."},{"name":"per_court","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":1,"description":"Players to return for each court.","default":6,"title":"Per Court"},"description":"Players to return for each court."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyLeadersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me/roster":{"put":{"tags":["fantasy"],"summary":"Save my current NBA Fantasy roster","description":"Saves the squad you own, which of the two games you play it in, the scoring system, the season-long league contract, an optional court override per player and the day you spent the Captain chip. The league contract is versioned with the squad, so editing a slot moves the revision exactly like a transfer does. Names are resolved against the season player index with the same accent-insensitive matcher the website uses, and a name that cannot be placed with certainty is refused with that name in the message rather than guessed at. The response carries a revision that moves only when the stored content changes, so a retry is safe. This changes Predixsport private state only: it never signs in to ESPN, Yahoo, Sleeper or the NBA game and never submits a lineup anywhere. Requires mcp:read and fantasy:nba:write. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"save_my_nba_fantasy_roster_v1_fantasy_nba_me_roster_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyRosterWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasySavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me/lineup":{"post":{"tags":["fantasy"],"summary":"Set tonight's lineup for my season-long NBA league","description":"Places the players you hold who have a game tonight into the slots your league actually fields, and returns the placement with the highest expected points. The answer is EXACT, not a ranking: your best guard can be worth nothing tonight if the only slots he fits are already taken by better guards, which is the mistake a player-by-player list makes and this endpoint does not. Every seat says where its number came from: tonight is the model's projection for tonight's game, and a player we hold nothing measured for carries a baseline estimate, never a zero. Each seat also carries `eligibility_confirmed`, which is false while the player's platform eligibility is still OUR guess from his listed position: say so wherever you show the placement. `locked` names the players whose game has already tipped off, so their SLOT cannot change: a lock keeps a player in the slot he is ALREADY playing in and the rest is optimised around him. Send `{\"player_name\": \"...\", \"slot\": \"UTIL\"}` when you know the seat. A bare name is resolved against the lineup last returned for the same day, and refused when there is none, because a guessed seat moves a slot. Season-long squads only. The official NBA Salary Cap game has its own Gameday card on GET /v1/fantasy/nba/me, and a nine-category league is refused because nine columns have no single score to maximise. Appends an immutable snapshot and changes nothing else. It never signs in to ESPN, Yahoo, Sleeper or the NBA game and never submits a lineup anywhere. Owner comes from the bearer token. Requires mcp:read and fantasy:nba:write. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"build_my_nba_fantasy_lineup_v1_fantasy_nba_me_lineup_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyLineupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyLineupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me/scenario":{"post":{"tags":["fantasy"],"summary":"Does one add and one drop beat keeping my NBA squad","description":"Compares keeping your saved squad with every single add and drop on the table, between now and your matchup end, and returns the best three moves beside the keep line. The comparison is between RE-OPTIMISED FULL LINEUPS day by day, never between two players' totals: a pickup only counts on a day he can actually reach one of your slots, so calendar congestion is counted by construction. Each move carries the expected points delta to the matchup end and a day-by-day breakdown showing what the incoming player adds inside your slots on each of them. Tonight's values are the model's projection for tonight's games. Every later day is the Predixsport weekly rule, which is built from season performance, recent form and the calendar. A player we hold nothing measured for is a baseline estimate and never a zero. A move whose outgoing player is one of the squad's three best by what he has actually done this season is marked `protected` and carries a `protected_reason`. NEVER present such a move without that sentence: a few days of calendar are not a reason to let a star go. Valid before the first tip-off tonight. Season-long squads only, and not for a nine-category league. Appends an immutable snapshot and changes nothing else: it never signs in to any platform and never makes a transaction for you. Owner comes from the bearer token. Requires mcp:read and fantasy:nba:write. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"plan_my_nba_fantasy_scenario_v1_fantasy_nba_me_scenario_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyScenarioRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyScenarioResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me/transfers":{"post":{"tags":["fantasy"],"summary":"Plan my transfers in the official NBA Salary Cap game","description":"Compares keeping your saved ten with one plan per number of moves, over the frozen prices of the official market, and returns the one worth the most AFTER the cost of any paid transfer. Each of those plans is the best squad on court that many moves can reach: one exact program per number of moves, over EVERY legal squad your bank can reach, with no shortlist in between. `search` says so, and says `truncated` if a solve ever has to stop early. Every legality check is the game's own and comes from the frozen market snapshot: the 100.0M cap, five back court and five front court, at most two players from one club, and only players the game will transact. The COURT is the game's, frozen with the price, not our listed position and not your own override: the game decides which side of the floor it counts a player on. Value is the Predixsport weekly rule, based on season performance, recent form and the calendar, over the games each player has left in the current Gameweek. Describe it in those words and never as a formula. A player the rule cannot value is never bought, and one you already own that it cannot value is kept or sold at zero with the case named in `status`. Every total is the FIVE THE GAME WOULD FIELD each Gameday of the window and never a sum of the ten: the game plays five of a squad of ten, so a signing who never reaches the floor is worth nothing and a paid transfer is recommended only when the fielded gain beats its cost. `proxy_points` is the old sum of all ten, kept beside it for provenance, and it decides nothing. The game gives two free transfers a Gameweek, they do not carry over, and each extra move costs 100 official points. Those numbers are on the official rules page and NOT in the market payload, which is why every response repeats `transfer_rules_source`. Each option carries `transfer_point_cost` and `net_expected_points`, and `gain_vs_keep` is measured after that cost. `sell_prices` lets you say what a sale really returns: the game charges a fee on the profit half of a sale and we do not hold your purchase price, so the default is the current price. `window.source` says whether the Gameweek is the game's own calendar or our ISO-week approximation of one. The plan runs ONLY while the official market is OPEN, which is a Gameday whose deadline is still ahead. Between two seasons the collector still freezes the board the game closed the last one with, and that board names players who have retired since, so this endpoint answers 409 with the sentence to show the owner rather than pricing anything on it. GET /v1/fantasy/nba/me carries the same state on `salary_cap.state`. Appends an immutable snapshot and changes nothing else. It never signs in to the NBA game and never makes a transfer for you. Owner comes from the bearer token. Requires mcp:read and fantasy:nba:write. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"plan_my_nba_fantasy_transfers_v1_fantasy_nba_me_transfers_post","parameters":[{"name":"prefer","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Send `respond-async` to get 202 and a job id at once instead of waiting, then read the result at /v1/fantasy/jobs/{job_id}.","title":"Prefer"},"description":"Send `respond-async` to get 202 and a job id at once instead of waiting, then read the result at /v1/fantasy/jobs/{job_id}."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyTransfersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasyTransfersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/nba/me/squad-builder":{"post":{"tags":["fantasy"],"summary":"Build a ten-player NBA Salary Cap squad from scratch","description":"Returns up to three legal ten-player squads within the budget, at the frozen prices of the official market, with five back court and five front court and at most two players from one club. The budget is the 100.0M cap less the `bank_to_leave` reserve, so it is the cap itself only when no reserve is asked for. Use it at the start of a season, or to see what a clean sheet would look like. It SAVES NO SQUAD: the answer is a proposal, and the owner's own roster only changes when he saves one through PUT /v1/fantasy/nba/me/roster. The valuation is deliberately NOT the weekly rule. At the start of a season that rule's recent form is last season's closing minutes and it ignores the opening price, which already carries the summer. A player with a season record is valued half on what he did and half on what his price implies for his court. A player without one, or with too few games to read as a record, is valued on the price line alone and `valuation_basis` says which of the two it was. At the start of a season that record is LAST season's, because ours holds nothing about a season that has not started, and `season_basis` on each row says which season it came from. Squads are valued over the first two official Gameweeks still ahead: for every Gameday of that window the best legal five of the ten, added up, which is what the game scores. The same ten are held for the whole window and no future transfer is simulated inside it, which is what `window_assumption` says. `window` lists the Gamedays and `gameweeks` on each squad says what each week contributed, so a squad that gives up the first one to win the second is visible. Two Gameweeks is a product default, not a measured optimum. `bank_after` is what is left of the cap with the `reserve` named beside it rather than subtracted from it twice. This call SOLVES rather than reads: three distinct squads over two whole Gameweeks of the full market, each its own exact program. It takes up to about 30 seconds, so give it a client timeout well above that and tell a waiting user what it is doing. `search` says how far the search got and it is three things. 'exact' means every legal squad the budget reaches was compared. 'certified' means it stopped as soon as it could prove it was inside its optimality tolerance, and `search_gap` says how close to the best squad there is every squad here was proved to be, against the `search_gap_tolerance` it was allowed. 'truncated' means the clock ran out before it could prove anything. On anything but 'exact', say so beside the squads and say how many squads came back, because fewer than three is the shape a search that stopped early takes. When our own NBA schedule does not yet cover every Gameday of that window, this answers 409 with the sentence to show the owner rather than valuing a squad over a week it cannot see. `bank_to_leave` keeps money back for later moves. `protected` names players the squad must contain whatever the numbers say. It runs ONLY while the official market is OPEN, which is a Gameday whose deadline is still ahead. Between two seasons the collector still freezes the board the game closed the last one with, and that board names players who have retired since, so this endpoint answers 409 with the sentence to show the owner rather than building a squad out of it. Appends an immutable snapshot and changes nothing else. It never signs in to the NBA game. Owner comes from the bearer token. Requires mcp:read and fantasy:nba:write. The NBA fantasy workspace is a public beta. When the operator narrows it to a private trial, this endpoint answers 403 for an account outside it.","operationId":"build_my_nba_fantasy_squad_v1_fantasy_nba_me_squad_builder_post","parameters":[{"name":"prefer","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Send `respond-async` to get 202 and a job id at once instead of waiting, then read the result at /v1/fantasy/jobs/{job_id}.","title":"Prefer"},"description":"Send `respond-async` to get 202 and a job id at once instead of waiting, then read the result at /v1/fantasy/jobs/{job_id}."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasySquadRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBAFantasySquadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/jobs/{job_id}":{"get":{"tags":["fantasy"],"summary":"Read one of my fantasy calculations","description":"The state of a calculation started with `Prefer: respond-async` (the NBA squad builder and the NBA transfer plan), or of one whose synchronous call answered 503 with a `Location` to this endpoint. `status` is `queued` (with `queue_position`, the jobs ahead of it), `running`, `done` or `failed`. When `done`, `result` is exactly the body the synchronous endpoint returns. When `failed`, `error` carries the status and the sentence the synchronous call would have answered. Poll every two or three seconds, a build takes up to about 30. A job is readable for six hours and only by the account that started it: anybody else gets 404. Reading it changes nothing and costs no quota.","operationId":"read_my_fantasy_job_v1_fantasy_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantasyJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/me":{"get":{"tags":["fantasy"],"summary":"Get my Fantacalcio squad, this matchweek's advice and my lineup","description":"Returns everything Predixsport holds for one of your Fantacalcio teams under this account: the 25 players you saved, the league rules you play under, which matchweek is open and how current its lineup information is, the latest immutable recommendation and the lineup you put on record for it. An account may keep more than one team, one per league. `teams` lists the ones that already hold a squad and `team` picks which one this response is about. A slot with no squad is not an error: it comes back with `roster: null` so a new team can be built in it. The recommendation carries, per player, his chance of starting, his projected fantavoto if he plays and his selection score, the first weighted by the second. The eleven is chosen on the lineup's expected score with substitutions (`selection_total`). `projected_points` is what the eleven is worth if everybody in it plays and it is the number to show a reader. `expected_defence` is the defence modifier and it is always presented separately, never folded into a player's line. `board_not_ready` is a STATE of the matchweek and carries the sentence to show: no projections published yet, the collection window has not opened, or the first match has started. An empty board between two matchweeks is the steady state, not an outage. `board_freshness` says `stale` only when a scheduled update was missed, never on plain age. `matchweeks` is the settled record of past giornate and is null unless this account has it enabled, which is not the same as an empty record. The owner comes exclusively from the bearer token and this endpoint does not accept a user id. The same squad is edited in the web workspace and through the MCP tools. Classic rules only, Mantra is not served. This never signs in to any fantasy platform and never submits a lineup anywhere. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"my_fantacalcio_state_v1_fantasy_fantacalcio_me_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}-\\d{2}$"},{"type":"null"}],"description":"Season label, e.g. 2026-27. Omitted = latest saved.","title":"Season"},"description":"Season label, e.g. 2026-27. Omitted = latest saved."},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":9,"minimum":1},{"type":"null"}],"description":"Which of your teams. Omitted = the first. Slots outside [1, 2, 3] are refused.","title":"Team"},"description":"Which of your teams. Omitted = the first. Slots outside [1, 2, 3] are refused."},{"name":"board_limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":25,"minimum":1},{"type":"null"}],"description":"Optional cap on how many players the recommendation returns. The starting eleven is always kept and the bench is trimmed behind it, with the total and omitted counts reported.","title":"Board Limit"},"description":"Optional cap on how many players the recommendation returns. The starting eleven is always kept and the bench is trimmed behind it, with the total and omitted counts reported."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/leaders":{"get":{"tags":["fantasy"],"summary":"Get this matchweek's best Serie A players per role, in order","description":"Returns the open matchweek's best players for each Classic role (P keeper, D defender, C midfielder, A forward), best first. The order is the selection score our lineup optimiser ranks on: the projected fantavoto weighted by the chance of starting, so a likely starter can rank above a better player who may sit on the bench. Only the ORDER is published, with each player's club, opponent and kick-off: no score, no probability and no lineup information. Players reported unavailable are left out. A ranking exists only while the matchweek is open for advice, from 48 hours before its first kick-off until that kick-off. Outside it the four lists are empty and `board_not_ready` carries the sentence to show, which between two matchweeks is the steady state, not an outage. `board.revision` changes whenever the projections are republished, and `board_freshness` says `stale` only when a scheduled update was missed. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"fantacalcio_role_leaders_v1_fantasy_fantacalcio_leaders_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}-\\d{2}$"},{"type":"null"}],"description":"Season label, e.g. 2026-27. Omitted = the current one.","title":"Season"},"description":"Season label, e.g. 2026-27. Omitted = the current one."},{"name":"per_role","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"How many players to return for each role.","default":5,"title":"Per Role"},"description":"How many players to return for each role."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioRoleLeadersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/players":{"get":{"tags":["fantasy"],"summary":"Find Serie A players and their Fantacalcio codes","description":"Searches this season's Fantacalcio player catalog and returns each match with the code that identifies him, his Classic role and his club. Use it before saving a squad: a squad is stored in CODES, because a printed name may be re-spelled between matchweeks while a code is stable across clubs and seasons. The search is accent insensitive and matches anywhere in the name, so `vlahovic` finds `Vlahović`. `role` narrows to P, D, C or A. Results are capped and the response says how many matched in total, so a broad search is visibly a sample rather than the whole list. This is a read of our own catalog and it publishes no statistics, no lineup information and no availability. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"search_fantacalcio_players_v1_fantasy_fantacalcio_players_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}-\\d{2}$"},{"type":"null"}],"description":"Season label, e.g. 2026-27. Omitted = the current one.","title":"Season"},"description":"Season label, e.g. 2026-27. Omitted = the current one."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Part of a player name. Omitted returns the first page.","title":"Q"},"description":"Part of a player name. Omitted returns the first page."},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[PDCApdca]$"},{"type":"null"}],"description":"Classic role: P keeper, D defender, C midfielder, A forward.","title":"Role"},"description":"Classic role: P keeper, D defender, C midfielder, A forward."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioCatalogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/me/roster":{"put":{"tags":["fantasy"],"summary":"Save one of my Fantacalcio squads","description":"Saves the 25 players you own in one team, as player codes, plus the Classic options your league changed, `substitution_mode` among them (traditional, hybrid or dynamic, hybrid when omitted). Every code is checked against this season's catalog and the ROLES come from that catalog, never from this request, which is what makes the 3 keepers, 8 defenders, 8 midfielders and 6 forwards check mean something. A code the catalog does not carry is refused with that code in the message rather than guessed at. Resolve names with GET /v1/fantasy/fantacalcio/players first. The save REPLACES that team's squad rather than adding to it, and it bumps a revision, which is how advice computed on the previous squad is marked stale. Saving one team never touches another. This changes Predixsport private state only. It never signs in to any fantasy platform and never changes your squad there. Requires mcp:read and fantasy:fantacalcio:write. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"save_my_fantacalcio_roster_v1_fantasy_fantacalcio_me_roster_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioRosterWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioSavedRoster"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/me/optimize":{"post":{"tags":["fantasy"],"summary":"Pick my best Fantacalcio lineup for the open matchweek","description":"Runs the exact Classic lineup optimiser over your saved squad and the open matchweek, and appends one immutable recommendation. The answer is a whole LINEUP, not a ranking: it chooses the formation, the eleven and the order of the bench together, because the substitution rule scores the bench in the order you leave it and a formation that fits your best players is worth more than any single pick. `module` pins one formation instead. `forced_in` treats a player as a sure starter and `forced_out` leaves him out. Both take player codes from your own squad, and a code you do not own is refused rather than quietly ignored. The optimiser maximises the lineup's expected score UNDER YOUR LEAGUE'S SUBSTITUTION RULE (`selection_total`): a starter who does not play is replaced from the bench the way your league's `substitution_mode` says (same role only, or a module change when no same-role reserve has a vote), up to your league's limit, so a better player with a real chance of starting can start over a surer one when the bench covers him. It never starts a player less likely than not to start unless you force him. `expected_substitutions` and `hole_probability` say what that risk costs: the latter is the probability of not fielding eleven voted players after every substitution your league's mode allows, and the answer echoes the `substitution_mode` it was priced under. Present the lineup with `projected_points`, which is what it is worth if everybody plays, and keep `expected_defence` as its own line. `what_if` says what would change if a doubtful bench player were confirmed, and `ballottaggi` names the closest call in each role. This is advice: it writes a snapshot on Predixsport and nothing else. It never signs in to any fantasy platform and never submits your lineup. Requires mcp:read and fantasy:fantacalcio:write. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"optimize_my_fantacalcio_lineup_v1_fantasy_fantacalcio_me_optimize_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioOptimizeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/me/lineup":{"post":{"tags":["fantasy"],"summary":"Put my own Fantacalcio lineup on record for this matchweek","description":"Records the eleven and the bench ORDER you actually set in your league, which may differ from ours. It is the pitch editor of the web workspace behind one call. Send every player in the recommendation exactly once, eleven as starters and the rest in the order you want them substituted in. The eleven must form a valid Classic formation with exactly one keeper. The bench order is scored, so it is not cosmetic. `snapshot_id` must be the latest recommendation for the matchweek, and the matchweek must still be open: once the first match starts the lineup on record can no longer be edited. This records your decision on Predixsport. It never signs in to any fantasy platform and never submits the lineup for you. Requires mcp:read and fantasy:fantacalcio:write. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"declare_my_fantacalcio_lineup_v1_fantasy_fantacalcio_me_lineup_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioLineupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioLineupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/fantasy/fantacalcio/me/recommendations/{snapshot_id}/apply":{"post":{"tags":["fantasy"],"summary":"Record that I set a Fantacalcio recommendation as my lineup","description":"After the owner confirms, records one immutable recommendation as the lineup on record for its matchweek. It is the shortcut for keeping our advice exactly as it is, where POST /v1/fantasy/fantacalcio/me/lineup records a lineup you changed. A retry is safe: `changed` is true whenever the lineup on record was not already this recommendation, so applying again after you declared your own eleven puts the recommendation back, and it answers `changed: false` when there is nothing to do. The matchweek it belongs to comes from the SNAPSHOT and never from the request, so one team's advice cannot land on another team. A changed squad, a refreshed board or a newer recommendation is refused rather than applied. This records your decision on Predixsport. It never signs in to any fantasy platform and never submits the lineup for you. Requires mcp:read and fantasy:fantacalcio:write. The Fantacalcio workspace is in a private beta: this endpoint answers 403 for an account outside it.","operationId":"apply_my_fantacalcio_recommendation_v1_fantasy_fantacalcio_me_recommendations__snapshot_id__apply_post","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"integer","title":"Snapshot Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioApplyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FantacalcioApplyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/teams/nba/{team}/prediction-performance":{"get":{"tags":["teams"],"summary":"How well the NBA models track one team","description":"Per-team reconciliation of our as-published game-day predictions against realized results. `team` accepts the franchise name (\"Boston Celtics\"), the slug (\"boston-celtics\") or a historical era name (\"Seattle SuperSonics\" resolves to the Thunder). Returns the team's predictability (binary Brier score of the win probability vs the NBA-wide average, with an 'on_script'/'in_line'/'chaos' tier), winner accuracy over all predicted games and over confident calls (65%+) next to the league baseline, upset counts (wins as a clear underdog, losses as a clear favorite), and the most recent reconciled games with the pre-game probabilities. NBA predictions exist only on game day, so the record is as-published by nature (`source='production'`). Typical use: judge how much to trust today's prediction for a team by checking how the model has tracked it this season.","operationId":"nba_team_prediction_performance_v1_teams_nba__team__prediction_performance_get","parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Team"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"description":"NBA season key: the closing year, e.g. 2026 = the 2025-26 season. Omitted = the team's latest covered season.","title":"Season"},"description":"NBA season key: the closing year, e.g. 2026 = the 2025-26 season. Omitted = the team's latest covered season."},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent reconciled games to return","default":10,"title":"Last"},"description":"How many recent reconciled games to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NBATeamPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/teams/euroleague/{team}/prediction-performance":{"get":{"tags":["teams"],"summary":"How well the Euroleague models track one club","description":"Per-club reconciliation of our game predictions against realized results. `team` accepts the canonical club name (\"Baskonia Vitoria-Gasteiz\"), the slug (\"baskonia-vitoria-gasteiz\"), the 3-letter club code (\"BAS\") or ANY historical sponsor name (\"Laboral Kutxa Vitoria\", \"TD Systems Baskonia Vitoria-Gasteiz\") — Euroleague club names carry the season's title sponsor and change almost every year, while the code stays put. Returns the club's predictability (binary Brier score of the win probability vs the Euroleague-wide average, with an 'on_script'/'in_line'/'chaos' tier), winner accuracy over all predicted games and over confident calls (65%+) next to the league baseline, upset counts (wins as a clear underdog, losses as a clear favourite), and the most recent reconciled games with the pre-game probabilities. Season keys are the Euroleague codes, e.g. `E2025` for 2025-26.","operationId":"euroleague_team_prediction_performance_v1_teams_euroleague__team__prediction_performance_get","parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":100,"title":"Team"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^E(19|20)\\d{2}$"},{"type":"null"}],"description":"Euroleague season code, e.g. E2025 = the 2025-26 season. Omitted = the club's latest covered season.","title":"Season"},"description":"Euroleague season code, e.g. E2025 = the 2025-26 season. Omitted = the club's latest covered season."},{"name":"last","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"How many recent reconciled games to return","default":10,"title":"Last"},"description":"How many recent reconciled games to return"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EuroLeagueTeamPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/ratings/{sport}":{"get":{"tags":["ratings"],"summary":"Current rating snapshot for a sport","description":"Returns the latest standings snapshot. NBA + football + EuroLeague populate per-team ratings with rank ordering (EuroLeague: ELO + Glicko-2 with rating-deviation, plus the current-season W/L record); tennis (ATP) and wta (women's tennis) return per-player ratings including surface-specific ELO (hard/clay/grass), form_index, and race ranking. Snapshot date reflects the last ratings-pipeline run for the relevant season.","operationId":"ratings_for_sport_v1_ratings__sport__get","parameters":[{"name":"sport","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SportEnum"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[A-Za-z]?[0-9_]{4,9}$"},{"type":"null"}],"description":"Season as stored in the AI pipeline (\"2026\" for NBA/tennis/wta, \"2025_2026\" for football, \"E2025\" for EuroLeague). Defaults to the most recent on file.","title":"Season"},"description":"Season as stored in the AI pipeline (\"2026\" for NBA/tennis/wta, \"2025_2026\" for football, \"E2025\" for EuroLeague). Defaults to the most recent on file."},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"description":"Football only — narrow to one league. Omit for cross-league snapshot.","title":"League"},"description":"Football only — narrow to one league. Omit for cross-league snapshot."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/NBARatingsResponse"},{"$ref":"#/components/schemas/EuroLeagueRatingsResponse"},{"$ref":"#/components/schemas/FootballRatingsResponse"},{"$ref":"#/components/schemas/TennisRatingsResponse"},{"$ref":"#/components/schemas/WTARatingsResponse"}],"title":"Response Ratings For Sport V1 Ratings  Sport  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/ratings/{sport}/history":{"get":{"tags":["ratings"],"summary":"ELO trajectory for a season","description":"Returns the ELO trajectory written by each ratings-pipeline run. NBA, EuroLeague and football give one point per team per matchday, ordered by date; tennis and wta give one point per player per match (a per-match Elo log), ordered by player then date. Trajectories are season-scoped; `rating_basis` in the response tells you how to read them — \"season_relative\" (top-flight football, EuroLeague) and \"absolute\" (NBA) both re-seed at 1500 each season (the NBA label refers to its fixed 1500-anchored scale, not to cross-season carryover), while second-division football seeds each season from the previous one regressed toward the mean (\"carryover\"). Tennis and wta are \"points_seeded\": season-scoped Elo re-seeded from tour entry points every January — never compare across seasons. Football history is league-scoped: the `league` parameter is required. Filter with `team` (substring of the team name; for tennis and wta this is the player-name filter and is strongly recommended, since a whole season is thousands of per-match rows; EuroLeague also accepts the club code) and `from`/`to` dates; page with `limit`/`offset` and `has_more`. The ratings dataset is published under CC BY 4.0 — reuse is welcome with a link to predixsport.com.","operationId":"ratings_history_for_sport_v1_ratings__sport__history_get","parameters":[{"name":"sport","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SportEnum"}},{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":10,"pattern":"^[A-Za-z]?[0-9_]{4,9}$"},{"type":"null"}],"description":"Season as stored in the AI pipeline (\"2026\" for NBA/tennis/wta, \"2025_2026\" for football, \"E2025\" for EuroLeague). Defaults to the most recent on file.","title":"Season"},"description":"Season as stored in the AI pipeline (\"2026\" for NBA/tennis/wta, \"2025_2026\" for football, \"E2025\" for EuroLeague). Defaults to the most recent on file."},{"name":"league","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/FootballLeagueEnum"},{"type":"null"}],"description":"Required for football (history is league-scoped); ignored otherwise.","title":"League"},"description":"Required for football (history is league-scoped); ignored otherwise."},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":60},{"type":"null"}],"description":"Filter to teams whose name contains this text (EuroLeague also matches the exact club code, e.g. 'MAD'). For tennis and wta this is the player-name filter — strongly recommended, as a whole season is thousands of per-match rows.","title":"Team"},"description":"Filter to teams whose name contains this text (EuroLeague also matches the exact club code, e.g. 'MAD'). For tennis and wta this is the player-name filter — strongly recommended, as a whole season is thousands of per-match rows."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Earliest matchday (YYYY-MM-DD), inclusive.","title":"From"},"description":"Earliest matchday (YYYY-MM-DD), inclusive."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Latest matchday (YYYY-MM-DD), inclusive.","title":"To"},"description":"Latest matchday (YYYY-MM-DD), inclusive."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Points per page (a league season is ~800 points, an NBA season ~2,500).","default":500,"title":"Limit"},"description":"Points per page (a league season is ~800 points, an NBA season ~2,500)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/NBARatingsHistoryResponse"},{"$ref":"#/components/schemas/EuroLeagueRatingsHistoryResponse"},{"$ref":"#/components/schemas/FootballRatingsHistoryResponse"},{"$ref":"#/components/schemas/TennisRatingsHistoryResponse"},{"$ref":"#/components/schemas/WTARatingsHistoryResponse"}],"title":"Response Ratings History For Sport V1 Ratings  Sport  History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/simulations/{sport}":{"get":{"tags":["simulations"],"summary":"Title forecasts from Monte Carlo simulations","description":"Latest forecast snapshot per active event. Tennis (ATP) and wta (women's tennis): each player carries `p_title` (share of 20,000 simulated tournaments won) and `p_reach` per round. NBA: each team carries `p_champion` (share of 10,000 simulated seasons won) and `p_reach` per stage (play-in, playoffs, conference semifinals/finals, finals, champion), plus a predicted playoff `bracket` with per-series win probabilities and series-score distributions (4-0 … 4-3). EuroLeague: each club carries `p_champion` (share of 10,000 simulated seasons won) and `p_reach` per stage (play-in, playoffs, final_four, finals, champion), plus the predicted `bracket` — Play-In, best-of-5 quarterfinals (3-0 … 3-2) and the neutral-site Final Four. Football: one forecast per domestic league across 12 leagues — 5 top flights (Serie A, Premier League, La Liga, Bundesliga, Ligue 1) and 7 second divisions (Championship, League One, League Two, Segunda División, Ligue 2, 2. Bundesliga, Serie B) — each club carries `p_champion`, expected final points/position (`expected_points`, `mean_rank`) and `p_reach` per TABLE BAND (top flights: champions_league, europa_league, relegation, plus relegation_playoff where the league has the slot; second divisions: auto_promotion, playoff_win, promoted, relegation, barrage_up/down where the format has them). Band sizes in `structure` (integer bands) and `structure_detail` (second-division seed/format layout). Football seasons are sampled as full scorelines, so draws and goal difference are modelled; there is no bracket — the final league table IS the outcome. Champions League: one forecast per season — each club carries `p_champion`, expected league-phase points/position (`expected_points`, `mean_rank`, both CONDITIONAL on reaching the league phase) and `p_reach` per stage on the fixed Swiss ladder (`playoff`, `top8`, `r16`, `quarter`, `semi`, `final`, `champion`). `playoff` means reached the knockout play-off round OR BETTER — a top-24 league-phase finish, so it includes the clubs that finish top 8 and skip it, and `playoff` >= `r16` always; `top8` is the separate direct-entry branch and is a subset of `r16`. Read `mode` and `provisional` before the numbers: on a `pre_draw` board (July/August, before UEFA's draw) the FIELD ITSELF is simulated, so `teams` covers the whole entrant universe — confirmed clubs plus clubs still playing qualifying, split by `entrant_status` and priced with `p_league_phase` — and is larger than 36; on a `fixtures` board it is the 36 drawn clubs with played matches pinned. Forecasts refresh on every prediction run: completed games are locked in and the remainder is re-simulated, so probabilities sharpen continuously. Snapshots older than `days` are considered archived and excluded.","operationId":"simulations_for_sport_v1_simulations__sport__get","parameters":[{"name":"sport","in":"path","required":true,"schema":{"$ref":"#/components/schemas/SportEnum"}},{"name":"tournament","in":"query","required":false,"schema":{"type":"string","maxLength":64,"pattern":"^[a-z0-9_]+$","description":"Tennis / WTA only: narrow to one tournament slug (e.g. `stuttgart`, `roland_garros`). Ignored for NBA and EuroLeague (one season-level forecast).","title":"Tournament"},"description":"Tennis / WTA only: narrow to one tournament slug (e.g. `stuttgart`, `roland_garros`). Ignored for NBA and EuroLeague (one season-level forecast)."},{"name":"league","in":"query","required":false,"schema":{"$ref":"#/components/schemas/FootballLeagueEnum","description":"Football only: narrow to one of the 12 leagues — top flights (`serie_a`, `premier_league`, `la_liga`, `bundesliga`, `ligue_1`) or second divisions (`championship`, `league_one`, `league_two`, `segunda_division`, `ligue_2`, `bundesliga_2`, `serie_b`). Omitted = all leagues. Ignored for other sports."},"description":"Football only: narrow to one of the 12 leagues — top flights (`serie_a`, `premier_league`, `la_liga`, `bundesliga`, `ligue_1`) or second divisions (`championship`, `league_one`, `league_two`, `segunda_division`, `ligue_2`, `bundesliga_2`, `serie_b`). Omitted = all leagues. Ignored for other sports."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"Freshness window in days on the forecast snapshot date.","default":7,"title":"Days"},"description":"Freshness window in days on the forecast snapshot date."},{"name":"team","in":"query","required":false,"schema":{"type":"string","minLength":2,"maxLength":100,"description":"Football and NBA: narrow the per-team rows to one club or franchise — football accepts the stored short name (`Man United`), display name (`Manchester United`) or slug (`manchester-united`); NBA accepts the franchise name (`Boston Celtics`) or slug (`boston-celtics`). Ignored for other sports.","title":"Team"},"description":"Football and NBA: narrow the per-team rows to one club or franchise — football accepts the stored short name (`Man United`), display name (`Manchester United`) or slug (`manchester-united`); NBA accepts the franchise name (`Boston Celtics`) or slug (`boston-celtics`). Ignored for other sports."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/TennisSimulationsResponse"},{"$ref":"#/components/schemas/WTASimulationsResponse"},{"$ref":"#/components/schemas/NBASimulationsResponse"},{"$ref":"#/components/schemas/EuroLeagueSimulationsResponse"},{"$ref":"#/components/schemas/FootballSimulationsResponse"},{"$ref":"#/components/schemas/ChampionsSimulationsResponse"}],"title":"Response Simulations For Sport V1 Simulations  Sport  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CalibrationResponse":{"properties":{"sport":{"type":"string","enum":["nba","euroleague","football","tennis","wta","champions"],"title":"Sport"},"model_id":{"type":"string","title":"Model Id"},"source_file":{"type":"string","title":"Source File","description":"Filename the payload was read from. Either the upstream ``*_deploy.json`` (preferred — carries a timestamp in the filename) or, when the upstream copy is missing, the blessed ``<short>.json`` synced to ``web/static/data/calibration``."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Sortable ISO timestamp (\"YYYY-MM-DD HH:MM:SS\") for the source. Parsed from the filename when read upstream; from the ``timestamp`` field in the JSON payload when read from the blessed copy. ``null`` when neither carries one."},"data":{"type":"object","title":"Data","description":"Calibration metrics as produced by the AI module's calculate_calibration pipeline. Shape depends on model type: binary classifiers return reliability bins; multiclass models return per_class arrays.\n\nREADING GUIDE — two independent metric families:\n• Calibration (does the stated probability match observed frequency?): `ece`, `calibration_mae`, `calibration_max_error`, `coverage_90`. A well-calibrated Poisson regressor has `coverage_90` ~0.90 and small `calibration_mae`; for noisy sport-stat regression `coverage_90` 0.78-0.85 with `calibration_mae` 0.05-0.10 is OPERATIONALLY ACCEPTABLE.\n• Accuracy (how close is the point estimate to truth?): `correlation`, `mae`, `rmse`, `brier_score`, `accuracy`. Independent of calibration — a low correlation on a Poisson regressor reflects high irreducible variance in the target, NOT miscalibration.\n\nThere is intentionally NO binary 'pass / fail' verdict in this payload: judging a model from a strict threshold misleads more than it informs on noisy sport-stat domains. Interpret the numeric metrics in context.\n\n`summary.source` names the upstream file stem the numbers were measured on. It matters where one model id has been served by successive training heads: the NBA per-player models read `player_points_full_roster` once the full-roster heads are deployed and `player_points` before that, and the metrics describe the population of the stem that is named."}},"type":"object","required":["sport","model_id","source_file","data"],"title":"CalibrationResponse"},"ChampionsActual":{"properties":{"result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result","description":"H, D or A on the 90-minute score."},"home_goals":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Home Goals"},"away_goals":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Away Goals"},"correct":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Correct","description":"Whether the published argmax matched the outcome. A single match is not evidence about a model — read `GET /v1/performance/champions` for the record."}},"type":"object","title":"ChampionsActual","description":"Realised outcome, present once the match has been played and ingested."},"ChampionsExpectedGoals":{"properties":{"home":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Home"},"away":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Away"},"caveat":{"type":"string","title":"Caveat","description":"Always present. Repeat it wherever these numbers are shown.","default":"Model goal rates, not a totals market: the goals family was measured and held back, and the level runs low (2.83 predicted vs 3.18 actual mean total on the holdout)."}},"type":"object","title":"ChampionsExpectedGoals","description":"The model's own Poisson rates. Context, NOT a totals market.\n\nThese are the two numbers the probability triple is built from, published\nbecause \"how open is this tie\" is a fair question the model can answer.\nTheir LEVEL is measured and biased low — predicted mean total 2.83 against\nan actual 3.18 over the holdout — so they order matches sensibly and do not\nproject goals accurately. Do not derive an over/under from them."},"ChampionsMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["champions"],"const":"champions","title":"Sport","default":"champions"},"competition":{"type":"string","title":"Competition","default":"ucl"},"season":{"type":"string","title":"Season"},"season_label":{"type":"string","title":"Season Label"},"match_date":{"type":"string","format":"date","title":"Match Date"},"kickoff_utc":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Kickoff Utc"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"},"home_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Country","description":"UEFA 3-letter association code, e.g. ENG."},"away_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Away Country"},"neutral_venue":{"type":"boolean","title":"Neutral Venue","default":false},"probabilities":{"$ref":"#/components/schemas/ChampionsProbabilities"},"expected_goals":{"$ref":"#/components/schemas/ChampionsExpectedGoals"},"ratings":{"$ref":"#/components/schemas/ChampionsRatings"},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of P(home win) in probability points, summing exactly to 100 x (P(home win) - baseline). `null` when the explanation could not be produced — it is advisory data on top of the prediction and never blocks one."},"produced_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Produced By","description":"Provenance of the fitted artifact behind this number: variant, shrink, draw coupling and training seasons. Named `produced_by` rather than `model_*` because pydantic reserves that prefix."},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At","description":"When this prediction was FIRST published (never moves)."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When it was last re-scored. Frozen at kick-off."},"actual":{"anyOf":[{"$ref":"#/components/schemas/ChampionsActual"},{"type":"null"}]}},"type":"object","required":["match_id","season","season_label","match_date","home_team","away_team","probabilities"],"title":"ChampionsMatchDetail","description":"Full Champions League match payload: 1X2, goal rates, key factors.\n\nScope note: TOURNAMENT matches only (league phase, knockout play-off, R16\nonwards). Qualifying rounds are ingested and feed the European Elo ladder,\nbut are outside the model's validated slice and are never published."},"ChampionsPerformance":{"properties":{"sport":{"type":"string","enum":["champions"],"const":"champions","title":"Sport","default":"champions"},"source":{"type":"string","enum":["backtest","production"],"title":"Source"},"slice_name":{"type":"string","title":"Slice Name","description":"Evaluated slice. 'ucl_tournament' = Champions League proper, qualifying rounds excluded.","default":"ucl_tournament"},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed matches graded."},"date_range":{"$ref":"#/components/schemas/DateRange"},"seasons":{"items":{"$ref":"#/components/schemas/SeasonPerformance"},"type":"array","title":"Seasons","description":"Per-season breakdown. The pooled metrics above are the n-weighted recombination of these rows — never a second, independently stored total."},"three_way_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Three Way Accuracy","description":"Share of matches whose highest 1X2 probability was the outcome."},"logloss":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Logloss","description":"Mean -log p(outcome). Lower is better."},"brier":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Brier","description":"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":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Rps","description":"Ranked Probability Score over the ordered (home, draw, away) triple, the standard football 1X2 proper score. Lower is better."},"ece_home":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Ece Home","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Note","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage Note","description":"Plain-language statement of what this record is and is not."}},"type":"object","required":["source","sample_size","date_range"],"title":"ChampionsPerformance","description":"UEFA Champions League 1X2 match-model record.\n\n``source`` is authoritative and, today, is always ``backtest``. Read the\nfield, not the endpoint: these matches were graded AFTER they were played,\nby a model refit on seasons through 2021 under a frozen protocol. That is\ngenuinely out-of-sample, and it is still not the same claim as \"published\nbefore kick-off\". No champions prediction existed in our database before\nthese matches started, so ``?source=production`` answers 404 rather than\nrelabelling this record."},"ChampionsPlayerForecast":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Public match id (file_name)."},"kickoff_utc":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Kickoff Utc"},"player_id":{"type":"string","title":"Player Id"},"player":{"type":"string","title":"Player"},"team":{"type":"string","title":"Team"},"opponent":{"type":"string","title":"Opponent"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"starter":{"type":"boolean","title":"Starter"},"mode":{"type":"string","title":"Mode","description":"t75 = conditioned on the official team sheet"},"expected_goals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Goals"},"probability_goal":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Probability Goal"},"expected_assists":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Assists","description":"Mean only; no assist PMF has passed the serving gate."},"expected_shots":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Shots"},"expected_shots_on_target":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Shots On Target"},"expected_recoveries":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Recoveries"},"expected_saves":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Saves"},"goals":{"$ref":"#/components/schemas/CountDistribution"},"shots":{"$ref":"#/components/schemas/CountDistribution"},"shots_on_target":{"$ref":"#/components/schemas/CountDistribution"},"recoveries":{"$ref":"#/components/schemas/CountDistribution"},"saves":{"anyOf":[{"$ref":"#/components/schemas/CountDistribution"},{"type":"null"}]},"model_note":{"type":"string","title":"Model Note"},"published_at":{"type":"string","format":"date-time","title":"Published At"}},"type":"object","required":["match_id","player_id","player","team","opponent","starter","mode","goals","shots","shots_on_target","recoveries","model_note","published_at"],"title":"ChampionsPlayerForecast"},"ChampionsPlayerForecastResponse":{"properties":{"forecasts":{"items":{"$ref":"#/components/schemas/ChampionsPlayerForecast"},"type":"array","title":"Forecasts"},"count":{"type":"integer","title":"Count"},"serving_mode":{"type":"string","title":"Serving Mode","default":"t75"},"data_scope":{"type":"string","title":"Data Scope","default":"Predixsport model outputs; no official game prices or ownership."},"usage_rights":{"type":"string","title":"Usage Rights","default":"Decision support for the authenticated customer. Named-player bulk redistribution requires separate written approval."}},"type":"object","required":["forecasts","count"],"title":"ChampionsPlayerForecastResponse"},"ChampionsPlayerPerformanceResponse":{"properties":{"player":{"type":"string","title":"Player"},"player_id":{"type":"string","title":"Player Id","description":"UEFA player id, the stable join key. A STRING with two width families (legacy 3-8 digits, modern 9) — never cast to an integer."},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"GOALKEEPER | DEFENDER | MIDFIELDER | FORWARD"},"season":{"type":"string","title":"Season","description":"UEFA seasonYear, the season ENDING year: '2026' = 2025-26"},"season_label":{"type":"string","title":"Season Label"},"source":{"type":"string","title":"Source","description":"'backtest' = frozen-protocol holdout (the whole season is out-of-sample); 'production' = live predictions reconciled after the matchday. A season is served from exactly one source."},"mode":{"type":"string","title":"Mode","description":"Serving mode the record was scored in. 't75' = the confirmed team sheet is out. Pre-match and T-75 score different row populations and are never pooled, so one mode is served."},"matches":{"type":"integer","title":"Matches"},"started":{"type":"integer","title":"Started","description":"Started appearances (the scored population)"},"minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minutes"},"predictability_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predictability Score","description":"Mean CRPS skill of shots + shots on target x 100. Higher = more predictable for our models. NULL for goalkeepers, who have no shot rows and are measured on saves instead."},"score_se":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score Se","description":"Standard error of predictability_score, same x100 units. A conservative bound (the two badge skills are treated as perfectly correlated). Always read the score with this."},"stats":{"items":{"$ref":"#/components/schemas/ChampionsStatPerformance"},"type":"array","title":"Stats"},"scorer":{"$ref":"#/components/schemas/ChampionsScorerPerformance"},"seasons":{"items":{"$ref":"#/components/schemas/ChampionsSeasonSummary"},"type":"array","title":"Seasons","description":"Every served season for this player, oldest first"}},"type":"object","required":["player","player_id","season","season_label","source","mode","matches","started","stats","scorer","seasons"],"title":"ChampionsPlayerPerformanceResponse"},"ChampionsProbabilities":{"properties":{"home_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Home Win"},"draw":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Draw"},"away_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Away Win"},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"H, D or A."}},"type":"object","title":"ChampionsProbabilities","description":"1X2 for a Champions League match. There is no second family here.\n\nThe model is a bivariate Poisson-goals GLM, so the same grid implies\ntotals, both-teams-score, Asian handicaps and exact score. All four were\nmeasured on the model's own frozen protocol and held back — totals and BTTS\nscore worse than the holdout base rate, handicap tails miss calibration by\nup to 18 points, exact score is marginal. Only 1X2 ships. Audit it with\n``GET /v1/calibration/champions/champions_winner``."},"ChampionsRatings":{"properties":{"home_euro_elo":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Euro Elo"},"away_euro_elo":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Away Euro Elo"}},"type":"object","title":"ChampionsRatings","description":"Pre-match European Elo, the model's single largest input.\n\nA dedicated ladder walked over Champions League AND Europa League matches,\nqualifying included, with career carryover — it is NOT re-seeded each season\nthe way the domestic football and NBA ratings are, so these values ARE\ncomparable across seasons."},"ChampionsScorerPerformance":{"properties":{"brier_model":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Brier Model"},"brier_naive":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Brier Naive"},"skill":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Skill","description":"1 - brier_model / brier_naive on the anytime-scorer market"},"goals":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Goals"},"expected_goals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Goals"}},"type":"object","title":"ChampionsScorerPerformance"},"ChampionsSearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["champions"],"const":"champions","title":"Sport","default":"champions"},"competition":{"type":"string","title":"Competition","default":"ucl"},"season":{"type":"string","title":"Season","description":"UEFA seasonYear (season ENDING year): `2027` = 2026-27."},"match_date":{"type":"string","format":"date","title":"Match Date"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"},"actual_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actual Result","description":"Realised 90-minute outcome once played: H, D or A. `null` while the match is still upcoming. A tie decided on penalties is a D here — that is what the 1X2 market and the model both mean."}},"type":"object","required":["match_id","season","match_date","home_team","away_team"],"title":"ChampionsSearchMatch","description":"One Champions League match in the searchable record.\n\nOnly TOURNAMENT matches are predicted (league phase, knockout play-off, R16\nonwards), so qualifying ties are absent from this index by design — see\n``ChampionsUpcomingMatch``."},"ChampionsSearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/ChampionsSearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["matches","count","has_more"],"title":"ChampionsSearchResponse"},"ChampionsSeasonSimulation":{"properties":{"season":{"type":"string","title":"Season","description":"UEFA season year, which is the season ENDING year: `2027` = the 2026-27 campaign."},"season_label":{"type":"string","title":"Season Label","description":"Human-readable season (`2026-27`)."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per day."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"mode":{"type":"string","title":"Mode","description":"Which branch produced this board, and therefore how to read it. `pre_draw` (July/August): the league-phase draw does not exist yet, so the field itself is simulated — `teams` covers the whole entrant universe (confirmed clubs PLUS clubs still in qualifying, see `entrant_status`), typically well over 36 rows. `fixtures` (late August onwards): the real draw is known, played matches are pinned at their real scores and only the remainder is sampled — 36 rows, all confirmed."},"provisional":{"type":"boolean","title":"Provisional","description":"True while the FIELD is not final (i.e. on every `pre_draw` board). Not a data-quality flag: the probabilities are complete and already integrate over who wins the remaining qualifying ties. It tells you the club list can still change.","default":false},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Engine status string: `PROVISIONAL`, `LIVE` (league phase in progress) or `LEAGUE_PHASE_COMPLETE`. Prefer the typed `mode` and `provisional` fields for logic."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated seasons behind the probabilities."},"n_scenarios":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Scenarios","description":"Provisional forecasts only: distinct qualifying scenarios sampled (each is then played out over several simulated seasons)."},"n_teams":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Teams","description":"Clubs covered by this forecast."},"league_phase_played":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"League Phase Played","description":"League-phase matches already played (post-draw forecasts)."},"league_phase_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"League Phase Remaining","description":"League-phase matches still to play (post-draw forecasts)."},"match_model_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Model Note","description":"Provenance of the match model behind the board: variant, calibration shrink and the seasons it was fitted on."},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline","description":"One-sentence framing published with a provisional board (how much of the field is already confirmed)."},"teams":{"items":{"$ref":"#/components/schemas/ChampionsTeamForecast"},"type":"array","title":"Teams","description":"Sorted by p_champion descending."}},"type":"object","required":["season","season_label","run_date","mode"],"title":"ChampionsSeasonSimulation"},"ChampionsSeasonSummary":{"properties":{"season":{"type":"string","title":"Season"},"season_label":{"type":"string","title":"Season Label"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"started":{"type":"integer","title":"Started"},"predictability_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predictability Score"}},"type":"object","required":["season","season_label","started"],"title":"ChampionsSeasonSummary"},"ChampionsSimulationsResponse":{"properties":{"sport":{"type":"string","enum":["champions"],"const":"champions","title":"Sport","default":"champions"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of season forecasts returned."},"seasons":{"items":{"$ref":"#/components/schemas/ChampionsSeasonSimulation"},"type":"array","title":"Seasons"}},"type":"object","required":["count","seasons"],"title":"ChampionsSimulationsResponse"},"ChampionsStatPerformance":{"properties":{"stat":{"type":"string","title":"Stat","description":"shots | sot | saves"},"label":{"type":"string","title":"Label"},"started":{"type":"integer","title":"Started","description":"Started appearances the stat was scored on"},"avg_expected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Expected"},"avg_actual":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Actual"},"crps_model":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crps Model"},"crps_naive":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crps Naive","description":"Naive baseline = role rate x expected minutes, level-matched on the fit era, with its own fitted dispersion. Stored rather than re-derived: it is a negative binomial over a minutes mixture, so no closed-form Poisson reproduces it."},"skill":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Skill","description":"1 - crps_model / crps_naive. Positive = our model tracks this player better than the baseline."},"skill_se":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Skill Se","description":"Standard error of `skill` (ratio-estimator delta method). Published because the skill is a ratio of small numbers and its variance is far wider for low-volume players, so a rank without it reads as precision the measurement does not have."},"in_score":{"type":"boolean","title":"In Score","description":"Whether this stat counts toward predictability_score"}},"type":"object","required":["stat","label","started","in_score"],"title":"ChampionsStatPerformance"},"ChampionsTeamForecast":{"properties":{"team":{"type":"string","title":"Team","description":"Club name as UEFA publishes it (e.g. `Bayern München`)."},"team_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Team Id","description":"UEFA numeric team id — the stable join key across seasons. Null for a club that has not yet appeared in the source feed."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"UEFA 3-letter association code (`GER`, `FRA`, `ENG`)."},"entrant_status":{"type":"string","title":"Entrant Status","description":"`confirmed` = the club is in the league phase (or already through to it). `pool` = the club is still playing qualifying and may not make the field at all — only ever present on a `pre_draw` (provisional) forecast.","default":"confirmed"},"modal_pot":{"anyOf":[{"type":"integer","maximum":4.0,"minimum":1.0},{"type":"null"}],"title":"Modal Pot","description":"Most frequent seeding pot (1-4) across the simulated scenarios. Null once the real draw exists (pots are then historical) and for a qualifying club that never reached the league phase."},"euro_elo":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Euro Elo","description":"European Elo ladder rating at forecast time — the club-strength input behind these probabilities."},"coefficient":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coefficient","description":"UEFA 5-year club coefficient used to seed the pots and the qualifying rounds. Present on provisional forecasts only."},"p_league_phase":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"P League Phase","description":"Probability the club plays the league phase at all. 1.0 for a confirmed entrant and for every club once the draw is made; below 1.0 only for clubs still in qualifying."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points","description":"Expected final league-phase points (simulation mean), CONDITIONAL on the club reaching the league phase — expected points for a club that is not playing is not a quantity. Compare with `p_reach`, which is unconditional."},"mean_rank":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Rank","description":"Mean simulated final league-phase position out of 36 (1 = top seed). Also CONDITIONAL on reaching the league phase."},"p_champion":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Champion","description":"Probability of winning the Champions League — the share of simulated seasons this club won. UNCONDITIONAL: a club still in qualifying carries its own elimination risk here. One measured caveat: club strengths are held fixed inside a simulated season, so a per-match edge compounds and an early (`pre_draw`) board likely overstates the favourites. Known engine limitation, under review."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of reaching each stage, UNCONDITIONAL, keyed by stage name. The Swiss format fixes the ladder, so the same keys are always present: `playoff`, `top8`, `r16`, `quarter`, `semi`, `final`, `champion`. Two keys need reading carefully. `playoff` means *reached the knockout play-off round OR BETTER*, i.e. a top-24 league-phase finish (survived the league phase) — the clubs that finish top 8 and skip the play-off are counted in it too, so `playoff` >= `r16` always. `top8` is the separate direct-entry branch (a top-8 league-phase finish sends a club straight to the round of 16), so it is a SUBSET of `r16` and the one key that does not decrease monotonically along the list (e.g. {\"playoff\": 0.99, \"top8\": 0.41, \"r16\": 0.96, \"quarter\": 0.58, \"champion\": 0.12})."}},"type":"object","required":["team","p_champion"],"title":"ChampionsTeamForecast"},"ChampionsUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Stable public id, e.g. `2026-09-16_arsenal_vs_bayern-munchen`. Use it as-is on `/v1/predictions/champions/{match_id}`."},"sport":{"type":"string","enum":["champions"],"const":"champions","title":"Sport","default":"champions"},"competition":{"type":"string","title":"Competition","description":"UEFA competition code. `ucl` is the only one served.","default":"ucl"},"season":{"type":"string","title":"Season","description":"UEFA seasonYear, the season ENDING year: `2027` is the 2026-27 campaign."},"season_label":{"type":"string","title":"Season Label","description":"Readable form, e.g. \"2026-27\"."},"match_date":{"type":"string","format":"date","title":"Match Date"},"kickoff_utc":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Kickoff Utc","description":"Scheduled kick-off in UTC."},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage","description":"UEFA stage code: GROUP_STANDINGS (league phase), FINAL_TOURNAMENT_PLAY_OFF (knockout play-off), ROUND_OF_16, QUARTER_FINALS, SEMIFINAL, FINAL."},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"},"neutral_venue":{"type":"boolean","title":"Neutral Venue","description":"True for the final, which is played at a venue assigned years in advance. The model switches its home-advantage term off on these matches, so home/away are administrative labels there.","default":false},"probability_home_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Home Win"},"probability_draw":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Draw"},"probability_away_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Away Win"},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"Argmax of the triple: H, D or A."},"expected_goals_home":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Expected Goals Home","description":"Model goal rate. Context, not a totals market."},"expected_goals_away":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Expected Goals Away","description":"Model goal rate. Context, not a totals market."},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At","description":"When this match's prediction was FIRST published. Predictions are re-scored daily until kick-off and then frozen; this timestamp does not move, so it is the publication lead against `kickoff_utc`."}},"type":"object","required":["match_id","season","season_label","match_date","home_team","away_team"],"title":"ChampionsUpcomingMatch","description":"One upcoming Champions League match, 1X2 only.\n\n**Scope.** Predictions cover the TOURNAMENT — the Swiss league phase, the\nknockout play-off and the round of 16 through the final. The July/August\nqualifying rounds are ingested and drive the European Elo ladder, but they\nare not predicted: the model's validated slice is the tournament, and a\nqualifying tie is dominated by clubs the ladder has seen a handful of times.\nBetween the end of one season and the league-phase draw (late August) this\nendpoint therefore returns an empty list. That is the steady state, not an\noutage.\n\n**One market, on purpose.** The Champions League model is a bivariate\nPoisson-goals GLM, and the same grid also implies totals, both-teams-score,\nAsian handicaps and exact score. Every one of those families was measured on\nthe model's own frozen protocol and held back: totals and BTTS score worse\nthan the base rate, the handicap tails are miscalibrated, exact score is\nmarginal. So only 1X2 is published here. There is no ``probability_over_2_5``\ntwin of the football schema, and its absence is a measurement, not an\nomission.\n\n``expected_goals_*`` are the model's own Poisson rates — the quantity the\nprobability triple is built from — and are published as context, never as a\ntotals line. Their LEVEL is known to run low (predicted mean total 2.83 vs\nan actual 3.18 on the holdout), so read them as an ordering of how open a tie\nis, not as a goal projection."},"ChampionsUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/ChampionsUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"ChampionsUpcomingResponse"},"ClubMarketAccuracy":{"properties":{"market":{"type":"string","title":"Market","description":"'match_result' (1X2), 'under_over_2_5' or 'btts'"},"club_accuracy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Club Accuracy","description":"% of the club's matches the as-published prediction called correctly"},"league_accuracy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"League Accuracy","description":"League-wide % over the same season and source"}},"type":"object","required":["market"],"title":"ClubMarketAccuracy"},"ClubPredictability":{"properties":{"brier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Brier","description":"Mean multiclass Brier score of our as-published 1X2 probabilities over the club's reconciled matches. Lower = the club's results were easier to call."},"league_brier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"League Brier","description":"Same average over every reconciled match in the league"},"delta_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delta Pct","description":"How much easier (+) or harder (-) the club was to call than its league average, as a percentage of the league Brier."},"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier","description":"'on_script' (clearly easier than the league), 'in_line', or 'chaos' (clearly harder). Null below 15 reconciled matches."}},"type":"object","title":"ClubPredictability"},"ClubRecentMatch":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Prediction file_name — join key across the API"},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"opponent":{"type":"string","title":"Opponent"},"is_home":{"type":"boolean","title":"Is Home"},"club_win_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Club Win Probability","description":"As-published win probability for THIS club (%)"},"draw_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Draw Probability"},"predicted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted","description":"Predicted outcome from the club's perspective: W / D / L"},"result":{"type":"string","title":"Result","description":"Realized outcome from the club's perspective"},"prediction_correct":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Prediction Correct"}},"type":"object","required":["match_id","opponent","is_home","result"],"title":"ClubRecentMatch"},"CountDistribution":{"properties":{"support":{"items":{"type":"integer"},"type":"array","title":"Support"},"probabilities":{"items":{"type":"number"},"type":"array","title":"Probabilities"}},"type":"object","required":["support","probabilities"],"title":"CountDistribution"},"DateRange":{"properties":{"earliest":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Earliest","description":"Earliest match date in the aggregated sample."},"latest":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Latest","description":"Latest match date in the aggregated sample."}},"type":"object","title":"DateRange"},"EuroLeagueActual":{"properties":{"home_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Points","description":"Points scored by the home team."},"away_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Away Points","description":"Points scored by the away team."},"point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Point Diff","description":"Realized (home_points - away_points). Compare with predicted_point_diff."},"total_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Points","description":"Realized combined points scored by both teams. Compare with predicted_total_points."},"result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result","description":"Which side won: 'Home_Win' or 'Away_Win'."}},"type":"object","title":"EuroLeagueActual","description":"Realized box-score result once the match has been played and\nreconciled (``euroleague_in_season_performance``). Absent until the\ngame is played."},"EuroLeagueDistributions":{"properties":{"spread":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Spread","description":"PMF over (home_points - away_points). Signed keys — negative means an away-favoured margin."},"total":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Total","description":"PMF over the combined total points scored by both teams."}},"type":"object","title":"EuroLeagueDistributions","description":"Game-level probability mass functions (the platform's differentiator)."},"EuroLeagueMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season"},"produced_by":{"type":"string","title":"Produced By","description":"Serving provenance. `cold_start_rating_prior_v1` is the disclosed early-season fallback (Rounds 1 to 6) built on team ratings and last season's scoring profile. Its accuracy is reported separately from the full-season model.","default":"mature_neural_v1"},"home_team":{"$ref":"#/components/schemas/TeamStats"},"away_team":{"$ref":"#/components/schemas/TeamStats"},"predicted_point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Point Diff","description":"Expected (home_points - away_points) from the dedicated spread regressor."},"predicted_total_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Total Points","description":"Expected sum of points scored by both teams (dedicated total regressor)."},"distributions":{"$ref":"#/components/schemas/EuroLeagueDistributions","description":"Full calibrated spread + total point distributions."},"players":{"items":{"$ref":"#/components/schemas/EuroLeaguePlayerProjection"},"type":"array","title":"Players","description":"Per-player projections (points/rebounds/assists/3PM/PIR + PMFs). Empty when no player rows exist."},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the winner prediction (positive values favour the home team). Factors: team strength (Elo & Glicko), scoring & shooting, rebounding & paint presence, ballhandling/steals/fouls, plus a synthetic home-advantage term. Absent for rows predicted before the explainability rollout."},"actual":{"anyOf":[{"$ref":"#/components/schemas/EuroLeagueActual"},{"type":"null"}],"description":"Realized box-score result, present once the match has been played and reconciled. Null for upcoming / unplayed matches — the predictive fields above are identical either way."}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"EuroLeagueMatchDetail"},"EuroLeaguePerformance":{"properties":{"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"source":{"type":"string","enum":["production","test"],"title":"Source"},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed games aggregated into this snapshot."},"date_range":{"$ref":"#/components/schemas/DateRange"},"winner_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Winner Accuracy","description":"Fraction of games where the winner classifier picked the correct side."},"point_diff_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Point Diff Mae","description":"Mean absolute error of (predicted_point_diff − actual_point_diff)."},"total_points_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Points Mae","description":"Mean absolute error of (predicted_total_points − actual_total_points)."}},"type":"object","required":["source","sample_size","date_range"],"title":"EuroLeaguePerformance"},"EuroLeaguePlayerPerformanceResponse":{"properties":{"player":{"type":"string","title":"Player","description":"Display name — the join key across the API"},"player_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Code","description":"Stable Euroleague player code from the box scores"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team","description":"Canonical, sponsor-free club name"},"team_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team Code","description":"3-letter club code"},"position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position","description":"Role on the club's official squad list: 'Guard', 'Forward' or 'Center'. Null when the squad list does not carry the player. The role is never inferred from the projection."},"season":{"type":"string","title":"Season","description":"Euroleague season key, e.g. 'E2025' = 2025-26"},"season_label":{"type":"string","title":"Season Label"},"source":{"type":"string","title":"Source","description":"'backtest' = completed season replayed with the deployed model on data it never trained on; 'production' = live predictions reconciled after each matchday (from 2026-27)."},"games_started":{"type":"integer","title":"Games Started"},"predictability_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predictability Score","description":"Mean CRPS skill of points + rebounds + PIR x 100. Higher = more predictable for the models. Assists and threes are reported but excluded from the score."},"stats":{"items":{"$ref":"#/components/schemas/PlayerStatPerformance"},"type":"array","title":"Stats","description":"One entry per projected stat: points, rebounds, assists, threes, pir. PIR is the Euroleague performance index rating and is the only stat here whose values can be negative."},"recent_games":{"items":{"$ref":"#/components/schemas/PlayerGameLine"},"type":"array","title":"Recent Games","description":"Most recent games first; started games only"}},"type":"object","required":["player","season","season_label","source","games_started","stats","recent_games"],"title":"EuroLeaguePlayerPerformanceResponse"},"EuroLeaguePlayerProjection":{"properties":{"name":{"type":"string","title":"Name"},"team":{"type":"string","title":"Team"},"is_home":{"type":"boolean","title":"Is Home"},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position","description":"Projected-starter slot, 1-5, used internally by the model. This is a rank, NOT a basketball position: it does not encode guard / forward / center. For a player's role see /v1/players/euroleague/{player}/prediction-performance."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points"},"expected_rebounds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Rebounds"},"expected_assists":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Assists"},"expected_three_pointers_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Three Pointers Made","description":"Expected three-pointers made."},"expected_pir":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Pir","description":"Expected Performance Index Rating (PIR) — EuroLeague's composite box-score metric: (points + rebounds + assists + steals + blocks + fouls drawn) − (missed FG + missed FT + turnovers + shots rejected + fouls committed). Can be negative."},"points_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Points Distribution"},"rebounds_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Rebounds Distribution"},"assists_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Assists Distribution"},"three_pointers_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Three Pointers Made Distribution"},"pir_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Pir Distribution","description":"Full PMF over PIR outcomes (signed — PIR can be negative)."}},"type":"object","required":["name","team","is_home"],"title":"EuroLeaguePlayerProjection","description":"Per-player projections for a EuroLeague match. Sourced from\n``euroleague_player_predictions``. Carries ``expected_pir`` — the\nPerformance Index Rating, EuroLeague's signature single-number box-score\nstat (no NBA equivalent) — alongside the standard prop set."},"EuroLeagueRatingHistoryPoint":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"Game date the rating was computed for."},"team_code":{"type":"string","title":"Team Code","description":"Three-letter EuroLeague club code."},"team_name":{"type":"string","title":"Team Name"},"elo_rating":{"type":"number","title":"Elo Rating","description":"Season-relative ELO entering the game (resets each season; negative = below the season-start baseline)."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played"}},"type":"object","required":["date","team_code","team_name","elo_rating","matches_played"],"title":"EuroLeagueRatingHistoryPoint"},"EuroLeagueRatingsHistoryResponse":{"properties":{"season":{"type":"string","title":"Season"},"rating_basis":{"type":"string","enum":["season_relative"],"const":"season_relative","title":"Rating Basis","default":"season_relative"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Points in this page."},"has_more":{"type":"boolean","title":"Has More","description":"True when further pages exist — advance `offset`."},"license":{"type":"string","title":"License","description":"Ratings-data license. Reuse permitted with attribution: link to https://www.predixsport.com","default":"CC BY 4.0"},"attribution":{"type":"string","title":"Attribution","default":"Reuse permitted with attribution: link to https://www.predixsport.com"},"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"points":{"items":{"$ref":"#/components/schemas/EuroLeagueRatingHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["season","count","has_more","points"],"title":"EuroLeagueRatingsHistoryResponse"},"EuroLeagueRatingsResponse":{"properties":{"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"season":{"type":"string","title":"Season"},"snapshot_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Date"},"teams":{"items":{"$ref":"#/components/schemas/EuroLeagueTeamRating"},"type":"array","title":"Teams"}},"type":"object","required":["season","teams"],"title":"EuroLeagueRatingsResponse"},"EuroLeagueSearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season"},"produced_by":{"type":"string","title":"Produced By","default":"mature_neural_v1"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"EuroLeagueSearchMatch"},"EuroLeagueSearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/EuroLeagueSearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More","description":"True if more results are available beyond limit+offset."}},"type":"object","required":["matches","count","has_more"],"title":"EuroLeagueSearchResponse"},"EuroLeagueSeasonSimulation":{"properties":{"season":{"type":"string","title":"Season","description":"EuroLeague season code (`E2025` = the 2025-26 season)."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per day."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated seasons behind the probabilities."},"rs_complete":{"type":"boolean","title":"Rs Complete","description":"Regular season (double round-robin) fully played.","default":false},"play_in_complete":{"type":"boolean","title":"Play In Complete","description":"Play-in round decided.","default":false},"is_completed":{"type":"boolean","title":"Is Completed","description":"True once the season has a champion.","default":false},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion","description":"Champion club name, when the season is finished."},"champion_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion Code","description":"Champion club code, when the season is finished."},"teams":{"items":{"$ref":"#/components/schemas/EuroLeagueTeamForecast"},"type":"array","title":"Teams","description":"Sorted by p_champion descending."},"bracket":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Bracket","description":"Predicted post-season bracket, keyed by slot id: `pi:a` / `pi:b` / `pi:c` (Play-In single games), `qf:1v8` / `qf:4v5` / `qf:3v6` / `qf:2v7` (best-of-5 quarterfinals), `ff:semi1` / `ff:semi2` and `ff:final` (neutral-site Final Four single games). Each slot lists the most likely matchups with the probability the matchup occurs (`p`), each side's win probability (`win`), and — for the best-of-5 quarterfinals only — the series-score distribution (`scores`, e.g. `\"3-1\": 0.28` = wins the series 3-1 in 28% of the simulations where the matchup happens). Team entities are full club names. Observed real games/series carry a `pinned` state."}},"type":"object","required":["season","run_date"],"title":"EuroLeagueSeasonSimulation"},"EuroLeagueSimulationsResponse":{"properties":{"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of season forecasts returned."},"seasons":{"items":{"$ref":"#/components/schemas/EuroLeagueSeasonSimulation"},"type":"array","title":"Seasons"}},"type":"object","required":["count","seasons"],"title":"EuroLeagueSimulationsResponse"},"EuroLeagueTeamForecast":{"properties":{"team":{"type":"string","title":"Team","description":"Full club name (e.g. `Real Madrid`)."},"team_code":{"type":"string","title":"Team Code","description":"Three-letter EuroLeague club code (e.g. `MAD`)."},"wins":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Wins","description":"Regular-season wins to date."},"losses":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Losses","description":"Regular-season losses to date."},"expected_rs_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Rs Wins","description":"Expected final regular-season wins (simulation mean)."},"p_champion":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Champion","description":"Probability of winning the EuroLeague title — the share of simulated seasons this club won."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of reaching each stage, keyed by stage name (`play_in`, `playoffs`, `final_four`, `finals`, `champion`; e.g. {\"playoffs\": 0.88, \"final_four\": 0.34, \"champion\": 0.08}). `play_in` = entering the play-in round. 1.0 = already there / guaranteed; 0.0 = ruled out."},"eliminated":{"type":"boolean","title":"Eliminated","description":"True only on pinned facts: the club lost an observed series or sits outside a decided bracket.","default":false}},"type":"object","required":["team","team_code","p_champion"],"title":"EuroLeagueTeamForecast"},"EuroLeagueTeamPerformanceResponse":{"properties":{"team":{"type":"string","title":"Team","description":"Canonical, sponsor-free club name. Euroleague club names carry the season's title sponsor and change almost every year, so this is the stable name and `team_code` the stable identifier."},"team_code":{"type":"string","title":"Team Code","description":"3-letter club code — the join key in every Euroleague table"},"slug":{"type":"string","title":"Slug"},"season":{"type":"string","title":"Season","description":"Euroleague season key, e.g. 'E2025' = 2025-26"},"season_label":{"type":"string","title":"Season Label"},"source":{"type":"string","title":"Source","description":"'backtest': the reconciled record replays a completed season with the deployed model on data it never trained on (train up to 2022-23, tuning on 2023-24, the whole 2025-26 season out-of-sample). 'production' will mark seasons predicted live, game by game, from 2026-27 onward."},"games":{"type":"integer","title":"Games","description":"Reconciled games behind the numbers"},"predictability":{"$ref":"#/components/schemas/TeamPredictability"},"markets":{"items":{"$ref":"#/components/schemas/TeamMarketAccuracy"},"type":"array","title":"Markets"},"upsets_caused":{"type":"integer","title":"Upsets Caused","description":"Games the club WON while our model gave it under 35% — wins as a clear underdog."},"upsets_suffered":{"type":"integer","title":"Upsets Suffered","description":"Games the club LOST while our model gave it 65% or more — losses as a clear favourite."},"recent_games":{"items":{"$ref":"#/components/schemas/TeamRecentGame"},"type":"array","title":"Recent Games","description":"Most recent reconciled games first"}},"type":"object","required":["team","team_code","slug","season","season_label","source","games","predictability","markets","upsets_caused","upsets_suffered","recent_games"],"title":"EuroLeagueTeamPerformanceResponse"},"EuroLeagueTeamRating":{"properties":{"name":{"type":"string","title":"Name","description":"Full club name as published by the EuroLeague (e.g. 'Real Madrid')."},"team_code":{"type":"string","title":"Team Code","description":"Three-letter EuroLeague club code (e.g. 'MAD')."},"elo_rank":{"type":"integer","minimum":1.0,"title":"Elo Rank","description":"Rank by `elo_rating` among current-season clubs (1 = strongest)."},"elo_rating":{"type":"number","title":"Elo Rating","description":"Predixsport ELO rating. Season-relative: it resets to 0 at the start of each season, so positive = above the season-start baseline and a value can be negative for a club performing below it. Higher = stronger."},"glicko_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Glicko Rank","description":"Rank by `glicko_rating` among current-season clubs (1 = strongest)."},"glicko_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko Rating","description":"Glicko-2 rating — unlike ELO this carries across seasons (centred near 1500) and pairs with `glicko_rd` to gauge confidence."},"glicko_rd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko Rd","description":"Rating Deviation — Glicko-2's uncertainty estimate. Lower = more confident; it rises across the off-season gap."},"glicko_vol":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko Vol","description":"Glicko-2 volatility — how erratic the club's recent results are."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played","description":"Games played this season.","default":0},"wins":{"type":"integer","minimum":0.0,"title":"Wins","default":0},"losses":{"type":"integer","minimum":0.0,"title":"Losses","default":0},"win_pct":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Win Pct","description":"wins / matches_played, in [0, 1].","default":0.0}},"type":"object","required":["name","team_code","elo_rank","elo_rating"],"title":"EuroLeagueTeamRating"},"EuroLeagueUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["euroleague"],"const":"euroleague","title":"Sport","default":"euroleague"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season","description":"EuroLeague season code, e.g. \"E2025\" (the 2025-26 season)."},"produced_by":{"type":"string","title":"Produced By","description":"Serving provenance. `cold_start_rating_prior_v1` is the disclosed early-season fallback (Rounds 1 to 6) built on team ratings and last season's scoring profile. Its accuracy is reported separately from the full-season model.","default":"mature_neural_v1"},"home_team":{"$ref":"#/components/schemas/TeamStats"},"away_team":{"$ref":"#/components/schemas/TeamStats"},"predicted_point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Point Diff","description":"Expected (home_points - away_points). Positive = home favoured. For `mature_neural_v1`, source is the dedicated Negative-Binomial spread regressor. Read `produced_by` before interpreting the row."}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"EuroLeagueUpcomingMatch","description":"One upcoming EuroLeague (European club basketball) match.\n\nLike NBA, each predicted metric comes from a SEPARATE, INDEPENDENTLY-TRAINED\nmodel (winner classifier, spread regressor, total-points regressor, plus\nper-player props — including PIR, the EuroLeague signature box-score stat —\non the match-detail endpoint). The models do not share a joint likelihood;\nsmall numeric mismatches between them are by design. Audit reliability per\nmodel via ``GET /v1/calibration/euroleague/<model_id>``; discover ids via\n``GET /v1/models?sport=euroleague``."},"EuroLeagueUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/EuroLeagueUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"EuroLeagueUpcomingResponse"},"EuroleagueFantasyAction":{"properties":{"player_code":{"type":"string","title":"Player Code"},"player_name":{"type":"string","title":"Player Name"},"from":{"type":"string","enum":["captain","starting_five","sixth_man","bench"],"title":"From"},"to":{"type":"string","enum":["captain","starting_five","sixth_man","bench"],"title":"To"},"played":{"type":"boolean","title":"Played"}},"additionalProperties":false,"type":"object","required":["player_code","player_name","from","to","played"],"title":"EuroleagueFantasyAction"},"EuroleagueFantasyApplyRequest":{"properties":{"confirm":{"type":"boolean","enum":[true],"const":true,"title":"Confirm","description":"Explicit confirmation to update only the saved Predixsport roster. The official Euroleague Fantasy game is never changed."}},"additionalProperties":false,"type":"object","required":["confirm"],"title":"EuroleagueFantasyApplyRequest"},"EuroleagueFantasyBoard":{"properties":{"season":{"type":"string","title":"Season"},"round_number":{"type":"integer","title":"Round Number"},"run_date":{"type":"string","title":"Run Date"},"revision":{"type":"string","pattern":"^[0-9a-f]{64}$","title":"Revision"},"turn_revision":{"type":"string","pattern":"^[0-9a-f]{64}$","title":"Turn Revision"},"open":{"type":"boolean","title":"Open"},"turn":{"type":"string","enum":["opening","between_turns","closed"],"title":"Turn"},"missing_result_count":{"type":"integer","minimum":0.0,"title":"Missing Result Count"},"players":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyBoardPlayer"},"type":"array","title":"Players"},"total_player_count":{"type":"integer","minimum":0.0,"title":"Total Player Count"},"omitted_player_count":{"type":"integer","minimum":0.0,"title":"Omitted Player Count"}},"additionalProperties":false,"type":"object","required":["season","round_number","run_date","revision","turn_revision","open","turn","missing_result_count","players","total_player_count","omitted_player_count"],"title":"EuroleagueFantasyBoard"},"EuroleagueFantasyBoardPlayer":{"properties":{"player_code":{"type":"string","title":"Player Code"},"player_name":{"type":"string","title":"Player Name"},"team_code":{"type":"string","title":"Team Code"},"position":{"type":"string","enum":["Guard","Forward","Center"],"title":"Position"},"game_date":{"type":"string","format":"date","title":"Game Date"},"expected_fantasy_points":{"type":"number","title":"Expected Fantasy Points"},"actual_settled":{"type":"boolean","title":"Actual Settled"},"actual_played":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Actual Played"},"actual_fantasy_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Actual Fantasy Points"}},"additionalProperties":false,"type":"object","required":["player_code","player_name","team_code","position","game_date","expected_fantasy_points","actual_settled"],"title":"EuroleagueFantasyBoardPlayer"},"EuroleagueFantasyManualResult":{"properties":{"played":{"type":"boolean","title":"Played"},"score":{"type":"number","title":"Score","default":0.0}},"additionalProperties":false,"type":"object","required":["played"],"title":"EuroleagueFantasyManualResult"},"EuroleagueFantasyMarketReference":{"properties":{"snapshot_id":{"type":"integer","title":"Snapshot Id"},"revision":{"type":"integer","title":"Revision"},"cutoff_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cutoff At"},"source_observed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Source Observed At"},"player_count":{"type":"integer","minimum":0.0,"title":"Player Count"}},"additionalProperties":false,"type":"object","required":["snapshot_id","revision","player_count"],"title":"EuroleagueFantasyMarketReference"},"EuroleagueFantasyOptimizeRequest":{"properties":{"season":{"type":"string","pattern":"^E20\\d{2}$","title":"Season"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","maxItems":10,"minItems":10,"title":"Roster"},"follow_turns":{"type":"boolean","title":"Follow Turns","description":"Opt in only if you will return between Turns to make changes. False builds the highest expected-score static opening lineup.","default":false},"manual_overrides":{"additionalProperties":{"$ref":"#/components/schemas/EuroleagueFantasyManualResult"},"type":"object","maxProperties":10,"title":"Manual Overrides"},"excluded_codes":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Excluded Codes"}},"additionalProperties":false,"type":"object","required":["season","roster"],"title":"EuroleagueFantasyOptimizeRequest"},"EuroleagueFantasyRecommendation":{"properties":{"snapshot_id":{"type":"integer","title":"Snapshot Id"},"round_number":{"type":"integer","title":"Round Number","description":"Round this applicable advice was saved for."},"board_run_date":{"type":"string","title":"Board Run Date"},"mode":{"type":"string","enum":["opening","between_turns","transfers"],"title":"Mode"},"policy_version":{"type":"string","title":"Policy Version"},"board_revision":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}],"title":"Board Revision"},"turn_revision":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}],"title":"Turn Revision"},"roster_revision":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Roster Revision"},"applied_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Applied At"},"recommendation":{"oneOf":[{"$ref":"#/components/schemas/EuroleagueFantasyRecommendationPayload"},{"$ref":"#/components/schemas/EuroleagueFantasyTransferPayload"}],"title":"Recommendation","discriminator":{"propertyName":"mode","mapping":{"between_turns":"#/components/schemas/EuroleagueFantasyRecommendationPayload","opening":"#/components/schemas/EuroleagueFantasyRecommendationPayload","transfers":"#/components/schemas/EuroleagueFantasyTransferPayload"}}},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["snapshot_id","round_number","board_run_date","mode","policy_version","recommendation","created_at"],"title":"EuroleagueFantasyRecommendation"},"EuroleagueFantasyRecommendationPayload":{"properties":{"mode":{"type":"string","enum":["opening","between_turns"],"title":"Mode"},"formation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Formation","description":"The game's formation selector value for this lineup, guards-forwards-centers on the floor, e.g. 3-1-1."},"calendar_adjusted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Calendar Adjusted"},"opening_strategy":{"anyOf":[{"type":"string","enum":["static","calendar"]},{"type":"null"}],"title":"Opening Strategy"},"expected_delta":{"type":"number","title":"Expected Delta"},"expected_final_points":{"type":"number","title":"Expected Final Points"},"probability_worse":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Probability Worse"},"probability_better":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Probability Better"},"actions":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyAction"},"type":"array","title":"Actions"},"no_change":{"type":"boolean","title":"No Change"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Roster"},"official_results":{"type":"integer","title":"Official Results"},"manual_results":{"type":"integer","title":"Manual Results"}},"additionalProperties":false,"type":"object","required":["mode","expected_delta","expected_final_points","actions","no_change","roster","official_results","manual_results"],"title":"EuroleagueFantasyRecommendationPayload"},"EuroleagueFantasyRosterPlayer":{"properties":{"position":{"type":"string","enum":["Guard","Forward","Center"],"title":"Position"},"position_index":{"type":"integer","title":"Position Index"},"player_code":{"type":"string","title":"Player Code"},"player_name":{"type":"string","title":"Player Name"},"slot":{"type":"string","enum":["captain","starting_five","sixth_man","bench"],"title":"Slot"},"excluded":{"type":"boolean","title":"Excluded","default":false},"result_settled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Result Settled"},"result_source":{"anyOf":[{"type":"string","enum":["official","manual_fallback"]},{"type":"null"}],"title":"Result Source"},"actual_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Actual Score"}},"additionalProperties":false,"type":"object","required":["position","position_index","player_code","player_name","slot"],"title":"EuroleagueFantasyRosterPlayer"},"EuroleagueFantasyRosterState":{"properties":{"season":{"type":"string","title":"Season"},"round_number":{"type":"integer","title":"Round Number"},"status":{"type":"string","enum":["opening","between_turns","final"],"title":"Status"},"revision":{"type":"integer","minimum":1.0,"title":"Revision","default":1},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Roster"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["season","round_number","status","roster","updated_at"],"title":"EuroleagueFantasyRosterState"},"EuroleagueFantasyRosterWrite":{"properties":{"season":{"type":"string","pattern":"^E20\\d{2}$","title":"Season"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","maxItems":10,"minItems":10,"title":"Roster"}},"additionalProperties":false,"type":"object","required":["season","roster"],"title":"EuroleagueFantasyRosterWrite"},"EuroleagueFantasyRoundState":{"properties":{"round_number":{"type":"integer","title":"Round Number"},"board_run_date":{"type":"string","title":"Board Run Date"},"status":{"type":"string","enum":["opening","in_progress","final"],"title":"Status"},"starting_roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Starting Roster"},"current_roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Current Roster"},"final_roster":{"anyOf":[{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array"},{"type":"null"}],"title":"Final Roster"},"final_player_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Final Player Score"},"final_score_source":{"anyOf":[{"type":"string","enum":["official_boxscore_v1","post_completion_edit_v1"]},{"type":"null"}],"title":"Final Score Source","description":"How the frozen score was earned. official_boxscore_v1 means the roster was standing before the Round's last tip-off. post_completion_edit_v1 means the roster was last written after that Round finished, so the score is not comparable to an official one."},"score_scope":{"type":"string","enum":["players_only"],"const":"players_only","title":"Score Scope","default":"players_only"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"finalized_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finalized At"}},"additionalProperties":false,"type":"object","required":["round_number","board_run_date","status","starting_roster","current_roster","updated_at"],"title":"EuroleagueFantasyRoundState"},"EuroleagueFantasySavedRoster":{"properties":{"season":{"type":"string","title":"Season"},"round_number":{"type":"integer","title":"Round Number"},"status":{"type":"string","enum":["opening","between_turns","final"],"title":"Status"},"revision":{"type":"integer","minimum":1.0,"title":"Revision","default":1},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Roster"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["season","round_number","status","roster","updated_at"],"title":"EuroleagueFantasySavedRoster"},"EuroleagueFantasyStateResponse":{"properties":{"sport":{"type":"string","title":"Sport","default":"euroleague"},"roster":{"$ref":"#/components/schemas/EuroleagueFantasyRosterState"},"board":{"anyOf":[{"$ref":"#/components/schemas/EuroleagueFantasyBoard"},{"type":"null"}]},"round_state":{"anyOf":[{"$ref":"#/components/schemas/EuroleagueFantasyRoundState"},{"type":"null"}]},"latest_recommendation":{"anyOf":[{"$ref":"#/components/schemas/EuroleagueFantasyRecommendation"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["roster"],"title":"EuroleagueFantasyStateResponse"},"EuroleagueFantasyTransferMove":{"properties":{"player_code":{"type":"string","title":"Player Code"},"player_name":{"type":"string","title":"Player Name"},"team_code":{"type":"string","title":"Team Code"},"position":{"type":"string","enum":["Guard","Forward","Center"],"title":"Position"},"price":{"type":"number","title":"Price","description":"Frozen market price in credits."},"expected_points":{"type":"number","title":"Expected Points","description":"Expected Round points on the visible form rule, zero when the rule cannot value the player."},"status":{"type":"string","enum":["form_valued","no_form_record","outside_projected_rotation"],"title":"Status"},"reason":{"type":"string","title":"Reason"}},"additionalProperties":false,"type":"object","required":["player_code","player_name","team_code","position","price","expected_points","status","reason"],"title":"EuroleagueFantasyTransferMove"},"EuroleagueFantasyTransferOption":{"properties":{"formation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Formation","description":"Formation selector value of this roster, e.g. 2-2-1."},"moves":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Moves"},"expected_points":{"type":"number","title":"Expected Points"},"gain_vs_hold":{"type":"number","title":"Gain Vs Hold"},"cost":{"type":"number","title":"Cost"},"cash_after":{"type":"number","title":"Cash After"},"incoming":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyTransferMove"},"type":"array","title":"Incoming"},"outgoing":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyTransferMove"},"type":"array","title":"Outgoing"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Roster"}},"additionalProperties":false,"type":"object","required":["moves","expected_points","gain_vs_hold","cost","cash_after","incoming","outgoing","roster"],"title":"EuroleagueFantasyTransferOption"},"EuroleagueFantasyTransferPayload":{"properties":{"mode":{"type":"string","enum":["transfers"],"const":"transfers","title":"Mode"},"champion":{"type":"string","enum":["form_blend_v1"],"const":"form_blend_v1","title":"Champion","description":"The visible transfer rule: the exact optimisation over the 50/50 mean of prior season-to-date and last-five PIR, times selection propensity, at frozen market prices. No model uplift is claimed."},"round_number":{"type":"integer","title":"Round Number"},"max_incoming":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Max Incoming"},"sale_value_basis":{"type":"string","enum":["current_price"],"const":"current_price","title":"Sale Value Basis"},"bank_before":{"type":"number","title":"Bank Before"},"roster_value":{"type":"number","title":"Roster Value"},"purchasing_power":{"type":"number","title":"Purchasing Power"},"hold":{"$ref":"#/components/schemas/EuroleagueFantasyTransferOption"},"options":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyTransferOption"},"type":"array","title":"Options"},"alternatives":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyTransferOption"},"type":"array","title":"Alternatives"},"recommended_moves":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Recommended Moves"},"recommended":{"$ref":"#/components/schemas/EuroleagueFantasyTransferOption"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","title":"Roster"},"no_change":{"type":"boolean","title":"No Change"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"},"market":{"$ref":"#/components/schemas/EuroleagueFantasyMarketReference"}},"additionalProperties":false,"type":"object","required":["mode","champion","round_number","max_incoming","sale_value_basis","bank_before","roster_value","purchasing_power","hold","options","recommended_moves","recommended","roster","no_change","counts","market"],"title":"EuroleagueFantasyTransferPayload"},"EuroleagueFantasyTransfersRequest":{"properties":{"season":{"type":"string","pattern":"^E20\\d{2}$","title":"Season"},"roster":{"items":{"$ref":"#/components/schemas/EuroleagueFantasyRosterPlayer"},"type":"array","maxItems":10,"minItems":10,"title":"Roster"},"bank_credits":{"type":"number","maximum":1000.0,"minimum":0.0,"title":"Bank Credits","description":"Credits currently in the bank on the official game, copied from the game, never guessed."},"max_incoming":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Max Incoming","description":"Moves you are willing to make. Defaults to the Round's allowance (4, or 10 in an unlimited window)."},"locked_codes":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Locked Codes","description":"Owned players to keep."},"excluded_codes":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Excluded Codes","description":"Owned players to sell."}},"additionalProperties":false,"type":"object","required":["season","roster","bank_credits"],"title":"EuroleagueFantasyTransfersRequest"},"FantacalcioApplyRequest":{"properties":{"confirm":{"type":"boolean","enum":[true],"const":true,"title":"Confirm","description":"Explicit owner confirmation. This records the advice as the lineup you set, on Predixsport only."}},"additionalProperties":false,"type":"object","required":["confirm"],"title":"FantacalcioApplyRequest"},"FantacalcioApplyResponse":{"properties":{"sport":{"type":"string","enum":["fantacalcio"],"const":"fantacalcio","title":"Sport","default":"fantacalcio"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"giornata":{"type":"integer","title":"Giornata"},"changed":{"type":"boolean","title":"Changed","description":"True whenever the lineup on record was not already this recommendation, so a confirmation that comes after you declared your own eleven puts the recommendation back. False only when the recommendation was already the lineup on record."},"applied_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Applied At"}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","giornata","changed"],"title":"FantacalcioApplyResponse"},"FantacalcioBallottaggio":{"properties":{"role":{"type":"string","enum":["P","D","C","A"],"title":"Role"},"last_in":{"type":"string","title":"Last In"},"first_out":{"type":"string","title":"First Out"},"gap":{"type":"number","title":"Gap"}},"additionalProperties":false,"type":"object","required":["role","last_in","first_out","gap"],"title":"FantacalcioBallottaggio","description":"The closest call in one role: last man in against first man out."},"FantacalcioBoard":{"properties":{"giornata":{"type":"integer","title":"Giornata"},"revision":{"type":"string","pattern":"^[0-9a-f]{64}$","title":"Revision","description":"Content revision, used to detect advice that went stale."},"run_id":{"type":"string","title":"Run Id"},"first_kickoff_utc":{"type":"string","format":"date-time","title":"First Kickoff Utc"},"last_kickoff_utc":{"type":"string","format":"date-time","title":"Last Kickoff Utc"},"player_count":{"type":"integer","title":"Player Count"}},"additionalProperties":false,"type":"object","required":["giornata","revision","run_id","first_kickoff_utc","last_kickoff_utc","player_count"],"title":"FantacalcioBoard","description":"Identity of the matchweek the advice is priced on."},"FantacalcioBoardFreshness":{"properties":{"status":{"type":"string","enum":["fresh","stale"],"title":"Status"},"collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Collected At"},"expected_slot":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expected Slot"}},"additionalProperties":false,"type":"object","required":["status"],"title":"FantacalcioBoardFreshness","description":"How current the published lineup information behind the board is.\n\n``stale`` means a scheduled collection slot came and went without an\nupdate, never plain age: the slots are 48, 24, 12 and 2 hours before the\nfirst kick-off, so a board can legitimately be a day old inside the gap\nbetween two of them."},"FantacalcioCatalogPlayer":{"properties":{"player_code":{"type":"string","title":"Player Code"},"name":{"type":"string","title":"Name"},"role":{"type":"string","enum":["P","D","C","A"],"title":"Role"},"club":{"type":"string","title":"Club"},"club_name":{"type":"string","title":"Club Name"}},"additionalProperties":false,"type":"object","required":["player_code","name","role","club","club_name"],"title":"FantacalcioCatalogPlayer"},"FantacalcioCatalogResponse":{"properties":{"sport":{"type":"string","enum":["fantacalcio"],"const":"fantacalcio","title":"Sport","default":"fantacalcio"},"season":{"type":"string","title":"Season"},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query"},"role":{"anyOf":[{"type":"string","enum":["P","D","C","A"]},{"type":"null"}],"title":"Role"},"players":{"items":{"$ref":"#/components/schemas/FantacalcioCatalogPlayer"},"type":"array","title":"Players"},"total_match_count":{"type":"integer","minimum":0.0,"title":"Total Match Count","description":"Matches before the result cap was applied."},"omitted_match_count":{"type":"integer","minimum":0.0,"title":"Omitted Match Count","default":0}},"additionalProperties":false,"type":"object","required":["season","players","total_match_count"],"title":"FantacalcioCatalogResponse"},"FantacalcioDeclaredLineup":{"properties":{"giornata":{"type":"integer","title":"Giornata"},"module":{"type":"string","title":"Module"},"starters":{"items":{"type":"string"},"type":"array","title":"Starters"},"bench_order":{"items":{"type":"string"},"type":"array","title":"Bench Order"},"provenance":{"type":"string","title":"Provenance","description":"declared is your own eleven, applied is ours after you confirmed it, assumed is ours standing in until you do either. Rows recorded before 21 September 2026 say declared for a confirmation too."},"declared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Declared At"},"snapshot_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Snapshot Id"}},"additionalProperties":false,"type":"object","required":["giornata","module","starters","bench_order","provenance"],"title":"FantacalcioDeclaredLineup","description":"The lineup on record for a matchweek."},"FantacalcioLeagueRules":{"properties":{"bonus_clean_sheet":{"anyOf":[{"type":"number","maximum":3.0,"minimum":0.0},{"type":"null"}],"title":"Bonus Clean Sheet","description":"Porta imbattuta: points the goalkeeper earns when he is fielded, gets a vote and his team concedes nothing. 0 (the default) means the league does not pay it, 1 is the value fantacalcio.it recommends. The forecast prices it as the bonus times the probability of a clean sheet, reported per player as clean_sheet_expected and never folded into a projected fantavoto."},"defence_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Defence Enabled","description":"Whether the league pays the defence modifier at all."},"defence_ladder":{"anyOf":[{"items":{"items":{"type":"number"},"type":"array","maxItems":2,"minItems":2},"type":"array","maxItems":20,"minItems":1},{"type":"null"}],"title":"Defence Ladder","description":"Pairs of [average pure vote, points], thresholds increasing from 0 to 10 and values non-decreasing from -30 to 30. Every value, negative ones included, is added to your own score. A modifier your league charges to the opponent instead is not modelled."},"defence_include_keeper":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Defence Include Keeper","description":"Whether the goalkeeper's pure vote enters the modifier average. True (the default) averages the keeper and the best three defenders, false averages the best four defenders and ignores the keeper."},"defence_min_defenders":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":3.0},{"type":"null"}],"title":"Defence Min Defenders","description":"Fielded defenders required before the modifier pays anything. Four is the Leghe Fantacalcio rule, so a three-defender formation earns nothing there. Three and five exist on other league managers."},"sv_value":{"anyOf":[{"type":"number","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Sv Value","description":"Vote given to a fielded player who did not get one. Null is the default (he scores nothing after substitutions), 6.0 is a '6 politico' league."},"max_subs":{"anyOf":[{"type":"integer","maximum":11.0,"minimum":0.0},{"type":"null"}],"title":"Max Subs","description":"How many substitutions the league makes in a matchweek. Three is the Classic default, Leghe Fantacalcio leagues often run five."},"substitution_mode":{"anyOf":[{"type":"string","enum":["traditional","hybrid","dynamic"]},{"type":"null"}],"title":"Substitution Mode","description":"How a starter who gets no vote is replaced. 'traditional': only a reserve of the same role enters, otherwise the slot stays empty. 'hybrid' (the default, the Leghe Fantacalcio 'Ibrido'): a reserve of the same role first, otherwise the first voted reserve in bench order that leaves one of the league's allowed formations, so a midfielder can replace a forward and 4-3-3 becomes 4-4-2. 'dynamic': bench order rules, the first voted reserve that leaves an allowed formation enters even when a same-role one sits lower. The goalkeeper is only ever replaced by a goalkeeper. On Leghe Fantacalcio it sits under league management, options, substitutions."},"bench_size":{"anyOf":[{"type":"integer","maximum":25.0,"minimum":0.0},{"type":"null"}],"title":"Bench Size","description":"Null means every non-starter sits on the bench, in order."},"modules":{"anyOf":[{"items":{"type":"string","pattern":"^[1-9]-[1-9]-[1-9]$"},"type":"array","maxItems":20,"minItems":1},{"type":"null"}],"title":"Modules","description":"The formations this league allows, e.g. ['3-4-3', '4-4-2']. The three outfield lines must add up to ten, and the list is enforced when a lineup is declared, not only when it is optimised."}},"additionalProperties":false,"type":"object","title":"FantacalcioLeagueRules","description":"The Classic options a league may change, all optional.\n\nAn omitted field keeps the engine default. The field names mirror\n``fantasy_solver/conf/fantacalcio.yml::league_overrides`` one for one and\na drift test holds them to it."},"FantacalcioLineupRequest":{"properties":{"snapshot_id":{"type":"integer","minimum":1.0,"title":"Snapshot Id"},"starters":{"items":{"type":"string","maxLength":40,"minLength":1},"type":"array","maxItems":11,"minItems":11,"title":"Starters"},"bench_order":{"items":{"type":"string","maxLength":40,"minLength":1},"type":"array","maxItems":25,"title":"Bench Order","description":"Every other player in the advice, in the order you want them substituted in. The order is scored, so it is not cosmetic."}},"additionalProperties":false,"type":"object","required":["snapshot_id","starters","bench_order"],"title":"FantacalcioLineupRequest"},"FantacalcioLineupResponse":{"properties":{"sport":{"type":"string","enum":["fantacalcio"],"const":"fantacalcio","title":"Sport","default":"fantacalcio"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"giornata":{"type":"integer","title":"Giornata"},"changed":{"type":"boolean","title":"Changed"}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","giornata","changed"],"title":"FantacalcioLineupResponse"},"FantacalcioMatchweek":{"properties":{"giornata":{"type":"integer","title":"Giornata"},"status":{"type":"string","enum":["open","locked","final"],"title":"Status"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"score_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Score Source"},"finalized_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finalized At"},"provenance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provenance"},"comparison":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioMatchweekComparison"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["giornata","status"],"title":"FantacalcioMatchweek"},"FantacalcioMatchweekComparison":{"properties":{"status":{"type":"string","title":"Status"},"recommended_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Recommended Score"},"delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delta","description":"Our advice minus the lineup that was played. Positive means the advice would have scored more."},"snapshot_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Snapshot Id"}},"additionalProperties":false,"type":"object","required":["status"],"title":"FantacalcioMatchweekComparison"},"FantacalcioModuleOption":{"properties":{"module":{"type":"string","title":"Module"},"expected_points":{"type":"number","title":"Expected Points"},"expected_defence":{"type":"number","title":"Expected Defence"}},"additionalProperties":false,"type":"object","required":["module","expected_points","expected_defence"],"title":"FantacalcioModuleOption"},"FantacalcioOptimizeRequest":{"properties":{"season":{"anyOf":[{"type":"string","pattern":"^20\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Season","description":"Omitted uses the latest season you saved."},"team":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Team"},"module":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Module","description":"Pin one formation, e.g. '4-4-2'. Omitted lets the optimiser choose among the formations your league allows."},"forced_in":{"items":{"type":"string","maxLength":40,"minLength":1},"type":"array","maxItems":11,"title":"Forced In","description":"Players to treat as sure starters, as codes from your own squad. A code you do not own is refused rather than ignored."},"forced_out":{"items":{"type":"string","maxLength":40,"minLength":1},"type":"array","maxItems":25,"title":"Forced Out","description":"Players to leave out, as codes from your own squad."}},"additionalProperties":false,"type":"object","title":"FantacalcioOptimizeRequest"},"FantacalcioPlayerAdvice":{"properties":{"player_code":{"type":"string","title":"Player Code"},"name":{"type":"string","title":"Name"},"role":{"type":"string","enum":["P","D","C","A"],"title":"Role"},"club":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Club"},"club_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Club Name"},"opponent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opponent"},"opponent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opponent Name"},"is_home":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Home"},"kickoff_utc":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Kickoff Utc"},"p_start":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Start","description":"Pooled probability that he is in the XI."},"projected_fantavoto":{"type":"number","title":"Projected Fantavoto","description":"His expected fantavoto IF he plays."},"selection_score":{"type":"number","title":"Selection Score","description":"projected_fantavoto weighted by p_start. Bench order follows it inside each role; the eleven itself is chosen on the lineup's expected score with substitutions (selection_total), so a better player with a real chance of starting can start over a surer one when the bench covers him. Never the score to quote a reader."},"expected_vote":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Vote"},"expected_goals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Goals"},"expected_assists":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Assists"},"expected_conceded":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Conceded","description":"Goalkeepers only."},"clean_sheet_expected":{"type":"number","title":"Clean Sheet Expected","description":"Expected porta imbattuta if he is fielded: the league's clean sheet bonus times the probability his team concedes nothing. 0 for every outfield player and for every league that does not pay the bonus. It is printed beside projected_fantavoto, never inside it: a team bonus is not part of a player's own fantavoto.","default":0.0},"opponent_effect":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Opponent Effect","description":"How much this fixture is worth to him against a league-median opponent. Positive is a friendlier fixture."},"unavailable":{"type":"boolean","title":"Unavailable","default":false},"availability_status":{"anyOf":[{"type":"string","enum":["reported_unavailable","no_probable_lineup_published"]},{"type":"null"}],"title":"Availability Status","description":"The only two availability states published. There is no count of lineup sources anywhere in this response."},"value_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value Source","description":"model, prior_season or role_prior."},"on_board":{"type":"boolean","title":"On Board","description":"False for an owned player this matchweek carries no projection for. He can only be advised onto the bench.","default":true},"forced":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forced","description":"Set when you asked for him in or out."},"out_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Out Reason","description":"Why a player is on the bench: titolarita (lower chance of starting), qualita (lower projection), titolarita_e_qualita, escluso (left out on request), ruolo non schierato, or copertura: his selection score beats a starter of his position, who starts because a reserve like him comes on if that starter gets no vote (see covers). Snapshots before 10 October 2026 may say modificatore for the same case."},"covers":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Covers","description":"With out_reason copertura: the player code of the starter of his position he is on the bench to cover."}},"additionalProperties":false,"type":"object","required":["player_code","name","role","p_start","projected_fantavoto","selection_score"],"title":"FantacalcioPlayerAdvice","description":"One player inside a recommendation."},"FantacalcioRecommendation":{"properties":{"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"team":{"type":"integer","title":"Team"},"giornata":{"type":"integer","title":"Giornata"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"applied_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Applied At"},"stale":{"type":"boolean","title":"Stale","description":"True when the squad or the board moved after this advice was computed. Run the optimiser again rather than acting on it.","default":false},"policy_version":{"type":"string","title":"Policy Version"},"module":{"type":"string","title":"Module"},"starters":{"items":{"type":"string"},"type":"array","maxItems":11,"minItems":11,"title":"Starters"},"bench_order":{"items":{"type":"string"},"type":"array","title":"Bench Order"},"selection_total":{"type":"number","title":"Selection Total","description":"The optimiser's own objective. Since policy_version fantacalcio-lineup-v3 it is the expected score of this eleven AND its bench under your league's substitution rule: a starter who does not play is replaced from the bench the way the league's substitution_mode says (same role only, or a module change when no same-role reserve has a vote), while your league's substitution limit lasts, plus the defence modifier weighted by the chance it is paid. Older snapshots (v2 and earlier) carry the plain sum of selection scores, which ignored substitutions."},"expected_substitutions":{"type":"number","minimum":0.0,"title":"Expected Substitutions","description":"How many substitutions the league's rule is expected to make for this lineup. 0 on snapshots older than fantacalcio-lineup-v3.","default":0.0},"hole_probability":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Hole Probability","description":"Probability of NOT fielding eleven voted players: at least one slot stays without a vote after every substitution the league's substitution_mode allows, because no eligible reserve is left on the bench or the substitution limit is spent. Since fantacalcio-lineup-v4 it follows the league's mode (a module change can fill a slot the same-role rule could not). 0 on snapshots older than fantacalcio-lineup-v3.","default":0.0},"substitution_mode":{"type":"string","enum":["traditional","hybrid","dynamic"],"title":"Substitution Mode","description":"The substitution rule this advice was priced under, echoed from the league rules at the time. Snapshots older than fantacalcio-lineup-v4 report 'traditional', the same-role walk they were computed with.","default":"traditional"},"projected_points":{"type":"number","title":"Projected Points","description":"What this eleven is worth if everybody in it plays: the starters' projected fantavoti plus the expected defence modifier. This is the number to show a reader."},"expected_defence":{"type":"number","title":"Expected Defence","description":"The defence modifier in points. Always shown separately."},"players":{"additionalProperties":{"$ref":"#/components/schemas/FantacalcioPlayerAdvice"},"type":"object","title":"Players"},"ballottaggi":{"items":{"$ref":"#/components/schemas/FantacalcioBallottaggio"},"type":"array","title":"Ballottaggi"},"what_if":{"items":{"$ref":"#/components/schemas/FantacalcioWhatIf"},"type":"array","title":"What If"},"modules":{"items":{"$ref":"#/components/schemas/FantacalcioModuleOption"},"type":"array","title":"Modules"},"player_count":{"type":"integer","minimum":0.0,"title":"Player Count"},"omitted_player_count":{"type":"integer","minimum":0.0,"title":"Omitted Player Count","default":0}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","team","giornata","created_at","policy_version","module","starters","bench_order","selection_total","projected_points","expected_defence","players","player_count"],"title":"FantacalcioRecommendation"},"FantacalcioRoleLeader":{"properties":{"rank":{"type":"integer","minimum":1.0,"title":"Rank","description":"1 is the best of his role this matchweek."},"player_code":{"type":"string","title":"Player Code"},"name":{"type":"string","title":"Name"},"role":{"type":"string","enum":["P","D","C","A"],"title":"Role"},"club":{"type":"string","title":"Club"},"club_name":{"type":"string","title":"Club Name"},"opponent":{"type":"string","title":"Opponent"},"opponent_name":{"type":"string","title":"Opponent Name"},"is_home":{"type":"boolean","title":"Is Home"},"kickoff_utc":{"type":"string","format":"date-time","title":"Kickoff Utc"}},"additionalProperties":false,"type":"object","required":["rank","player_code","name","role","club","club_name","opponent","opponent_name","is_home","kickoff_utc"],"title":"FantacalcioRoleLeader","description":"One player in the matchweek's role ranking: a position, never a score."},"FantacalcioRoleLeaders":{"properties":{"P":{"items":{"$ref":"#/components/schemas/FantacalcioRoleLeader"},"type":"array","title":"P"},"D":{"items":{"$ref":"#/components/schemas/FantacalcioRoleLeader"},"type":"array","title":"D"},"C":{"items":{"$ref":"#/components/schemas/FantacalcioRoleLeader"},"type":"array","title":"C"},"A":{"items":{"$ref":"#/components/schemas/FantacalcioRoleLeader"},"type":"array","title":"A"}},"additionalProperties":false,"type":"object","required":["P","D","C","A"],"title":"FantacalcioRoleLeaders","description":"The four Classic roles, each ranked best first."},"FantacalcioRoleLeadersResponse":{"properties":{"sport":{"type":"string","enum":["fantacalcio"],"const":"fantacalcio","title":"Sport","default":"fantacalcio"},"season":{"type":"string","title":"Season"},"per_role":{"type":"integer","title":"Per Role"},"board":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioBoard"},{"type":"null"}]},"board_not_ready":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Not Ready"},"board_freshness":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioBoardFreshness"},{"type":"null"}]},"roles":{"$ref":"#/components/schemas/FantacalcioRoleLeaders"}},"additionalProperties":false,"type":"object","required":["season","per_role","roles"],"title":"FantacalcioRoleLeadersResponse"},"FantacalcioRosterEntry":{"properties":{"player_code":{"type":"string","maxLength":40,"minLength":1,"title":"Player Code"},"credits":{"type":"integer","maximum":500.0,"minimum":0.0,"title":"Credits","description":"What this player cost at the auction. 0 when unknown.","default":0}},"additionalProperties":false,"type":"object","required":["player_code"],"title":"FantacalcioRosterEntry","description":"One owned player on the way IN. The role is never accepted here."},"FantacalcioRosterWrite":{"properties":{"season":{"type":"string","pattern":"^20\\d{2}-\\d{2}$","title":"Season"},"team":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Team","description":"Team slot. Omitted saves the first one."},"team_name":{"type":"string","maxLength":60,"minLength":1,"pattern":"\\S","title":"Team Name"},"mode":{"type":"string","enum":["classic"],"const":"classic","title":"Mode","description":"Only Classic is served. Mantra is not.","default":"classic"},"roster":{"items":{"$ref":"#/components/schemas/FantacalcioRosterEntry"},"type":"array","maxItems":25,"minItems":25,"title":"Roster","description":"The 25 players you own, as player codes. Roles come from the season catalog, never from this request, and the squad must hold 3 keepers, 8 defenders, 8 midfielders and 6 forwards once those roles are applied."},"league_rules":{"$ref":"#/components/schemas/FantacalcioLeagueRules","description":"Only the Classic options your league changed."}},"additionalProperties":false,"type":"object","required":["season","team_name","roster"],"title":"FantacalcioRosterWrite"},"FantacalcioSavedPlayer":{"properties":{"player_code":{"type":"string","title":"Player Code"},"role":{"type":"string","enum":["P","D","C","A"],"title":"Role"},"name":{"type":"string","title":"Name"},"club":{"type":"string","title":"Club","description":"Club identifier, the join key of the module."},"club_name":{"type":"string","title":"Club Name","description":"The same club as a label."},"credits":{"type":"integer","title":"Credits"}},"additionalProperties":false,"type":"object","required":["player_code","role","name","club","club_name","credits"],"title":"FantacalcioSavedPlayer","description":"One owned player on the way OUT, with the catalog's own role and club."},"FantacalcioSavedRoster":{"properties":{"season":{"type":"string","title":"Season"},"team":{"type":"integer","title":"Team"},"mode":{"type":"string","title":"Mode"},"team_name":{"type":"string","title":"Team Name"},"status":{"type":"string","title":"Status"},"revision":{"type":"integer","title":"Revision"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"roster":{"items":{"$ref":"#/components/schemas/FantacalcioSavedPlayer"},"type":"array","title":"Roster"},"league_rules":{"$ref":"#/components/schemas/FantacalcioLeagueRules"}},"additionalProperties":false,"type":"object","required":["season","team","mode","team_name","status","revision","updated_at","roster","league_rules"],"title":"FantacalcioSavedRoster"},"FantacalcioStateResponse":{"properties":{"sport":{"type":"string","enum":["fantacalcio"],"const":"fantacalcio","title":"Sport","default":"fantacalcio"},"season":{"type":"string","title":"Season"},"team":{"type":"integer","title":"Team"},"teams":{"items":{"$ref":"#/components/schemas/FantacalcioTeamSummary"},"type":"array","title":"Teams"},"roster":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioSavedRoster"},{"type":"null"}],"description":"Null for a team slot this account has not filled yet."},"board":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioBoard"},{"type":"null"}]},"board_not_ready":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Not Ready","description":"Present when no advice can be given yet, with the reason to show the reader. It is a state of the matchweek, not a failure."},"board_freshness":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioBoardFreshness"},{"type":"null"}]},"latest_recommendation":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioRecommendation"},{"type":"null"}]},"declared_lineup":{"anyOf":[{"$ref":"#/components/schemas/FantacalcioDeclaredLineup"},{"type":"null"}]},"matchweeks":{"anyOf":[{"items":{"$ref":"#/components/schemas/FantacalcioMatchweek"},"type":"array"},{"type":"null"}],"title":"Matchweeks","description":"The matchweek record. Null means this account does not have it enabled, which is not the same as an empty record."}},"additionalProperties":false,"type":"object","required":["season","team"],"title":"FantacalcioStateResponse"},"FantacalcioTeamSummary":{"properties":{"team":{"type":"integer","title":"Team"},"mode":{"type":"string","title":"Mode"},"team_name":{"type":"string","title":"Team Name"},"revision":{"type":"integer","title":"Revision"},"players":{"type":"integer","title":"Players"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["team","mode","team_name","revision","players","updated_at"],"title":"FantacalcioTeamSummary","description":"One team slot this account already has a squad in."},"FantacalcioWhatIf":{"properties":{"player_code":{"type":"string","title":"Player Code"},"replaces":{"items":{"type":"string"},"type":"array","title":"Replaces"},"gain":{"type":"number","title":"Gain"}},"additionalProperties":false,"type":"object","required":["player_code","replaces","gain"],"title":"FantacalcioWhatIf","description":"What changes if a doubtful bench player were confirmed to start."},"FantasyJobError":{"properties":{"status_code":{"type":"integer","title":"Status Code","description":"The status the synchronous call would have answered (400, 404, 409 or 503)."},"detail":{"type":"string","title":"Detail","description":"The sentence to show the owner."}},"additionalProperties":false,"type":"object","required":["status_code","detail"],"title":"FantasyJobError"},"FantasyJobResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"kind":{"type":"string","title":"Kind"},"status":{"type":"string","enum":["queued","running","done","failed"],"title":"Status"},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position"},"result":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Result","description":"When `done`: the exact body the synchronous endpoint returns (NBAFantasySquadResponse for nba.squad_builder, NBAFantasyTransfersResponse for nba.transfers)."},"error":{"anyOf":[{"$ref":"#/components/schemas/FantasyJobError"},{"type":"null"}],"description":"When `failed`: what the synchronous call would have answered."}},"additionalProperties":false,"type":"object","required":["job_id","kind","status"],"title":"FantasyJobResponse"},"FootballActual":{"properties":{"result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result","description":"Full-time result: 'H' (home win), 'D' (draw) or 'A' (away win)."},"total_goals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Goals","description":"Total goals scored by both teams."},"under_over_2_5":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Under Over 2 5","description":"Realized under/over 2.5 goals: 'under_2_5' or 'over_2_5'."},"btts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Btts","description":"Both-teams-to-score outcome: 'goal' or 'no_goal'."}},"type":"object","title":"FootballActual","description":"Realized result once the match has been played and reconciled (the\nper-league ``football_in_season_performance*`` table). Absent until the\nmatch is played."},"FootballClubPerformanceResponse":{"properties":{"club":{"type":"string","title":"Club","description":"Stored club name — the join key across the API"},"display_name":{"type":"string","title":"Display Name"},"slug":{"type":"string","title":"Slug"},"league":{"type":"string","title":"League","description":"Customer-facing league code, e.g. 'serie_a'"},"league_name":{"type":"string","title":"League Name"},"season":{"type":"string","title":"Season","description":"Season key, e.g. '2025_2026'"},"season_label":{"type":"string","title":"Season Label"},"season_is_current":{"type":"boolean","title":"Season Is Current","description":"True when `season` is the latest covered season. False means the numbers belong to an earlier season — either because you asked for one, or because the current season has no or too few reconciled matches yet (early season, five-matchday warm-up) and the club's previous full record is served instead. Do not present a False payload as the club's current-season form."},"source":{"type":"string","title":"Source","description":"'production' = predictions as originally published before each match (frozen record); 'backtest' = a league's first covered season, reconstructed at launch"},"matches":{"type":"integer","title":"Matches","description":"Reconciled matches behind the numbers"},"predictability":{"$ref":"#/components/schemas/ClubPredictability"},"markets":{"items":{"$ref":"#/components/schemas/ClubMarketAccuracy"},"type":"array","title":"Markets"},"recent_matches":{"items":{"$ref":"#/components/schemas/ClubRecentMatch"},"type":"array","title":"Recent Matches","description":"Most recent reconciled matches first"}},"type":"object","required":["club","display_name","slug","league","league_name","season","season_label","season_is_current","source","matches","predictability","markets","recent_matches"],"title":"FootballClubPerformanceResponse"},"FootballDistributions":{"properties":{"corners":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Corners"},"shots":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Shots"},"spread":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Spread"}},"type":"object","title":"FootballDistributions","description":"Probability mass functions for count-style targets."},"FootballLeagueEnum":{"type":"string","enum":["serie_a","premier_league","la_liga","bundesliga","ligue_1","championship","league_one","league_two","segunda_division","ligue_2","bundesliga_2","serie_b"],"title":"FootballLeagueEnum"},"FootballLeagueSimulation":{"properties":{"league":{"type":"string","title":"League","description":"League code. 12 leagues: 5 top flights (`serie_a`, `premier_league`, `la_liga`, `bundesliga`, `ligue_1`) and 7 second divisions (`championship`, `league_one`, `league_two`, `segunda_division`, `ligue_2`, `bundesliga_2`, `serie_b`)."},"league_name":{"type":"string","title":"League Name","description":"Display name (e.g. `Serie A`)."},"season":{"type":"string","title":"Season","description":"Season code (`2025_2026` = 2025-26)."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per run."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated seasons behind the probabilities."},"n_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Remaining","description":"League fixtures still to be played at snapshot time."},"season_complete":{"type":"boolean","title":"Season Complete","description":"Every league fixture has been played.","default":false},"is_completed":{"type":"boolean","title":"Is Completed","description":"True once the season has a champion.","default":false},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion","description":"Champion club name, when the season is finished."},"structure":{"additionalProperties":{"type":"integer"},"type":"object","title":"Structure","description":"Integer band sizes used for this forecast. Top flights key this on qualification/relegation, e.g. {\"champions_league\": 4, \"europa_league\": 2, \"relegation\": 3, \"relegation_playoff\": 0} (counts follow each league's real format and UEFA allocation). Second divisions carry the integer promotion/relegation band sizes here, e.g. {\"auto_promotion\": 2, \"relegation\": 3, \"playout_max_gap\": 3}; their playoff/play-out/barrage seed ranges and playoff format (non-integer values) live in `structure_detail`."},"structure_detail":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Structure Detail","description":"Second-division-only: the full promotion/relegation format layout when it cannot be expressed as plain integer band sizes. Present for second divisions, null for top flights. Keys: `auto_promotion` (int), `playoff_seeds` ([lo, hi] table positions entering the promotion playoff, or null), `playoff_format` (e.g. `two_leg_final`, `serie_b`), `relegation` (int), `playout_seeds` ([lo, hi] for a relegation play-out, or null), `playout_max_gap` (int or null), `barrage_up_seeds` / `barrage_down_seeds` (promotion/relegation barrage position pairs, or null). Additive — read only the keys present."},"teams":{"items":{"$ref":"#/components/schemas/FootballTeamForecast"},"type":"array","title":"Teams","description":"Sorted by mean simulated final position (best first)."}},"type":"object","required":["league","league_name","season","run_date"],"title":"FootballLeagueSimulation"},"FootballMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"league":{"type":"string","title":"League"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"},"probabilities":{"$ref":"#/components/schemas/FootballProbabilities"},"expected_corners":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Corners"},"expected_shots":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Shots"},"expected_spread":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Spread","description":"Expected (home_goals - away_goals). Positive = home favoured."},"distributions":{"$ref":"#/components/schemas/FootballDistributions"},"cdfs":{"$ref":"#/components/schemas/FootballDistributions","description":"Cumulative distribution functions for the same count-style targets."},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the home-vs-away edge (the draw is modelled separately). Absent for rows predicted before the explainability rollout."},"key_factors_under_over":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the under/over 2.5 goals prediction: factor directions are 'under'/'over' and per-team factors carry a 'side' field. Independent of key_factors (a separate model). Absent for rows predicted before the rollout."},"actual":{"anyOf":[{"$ref":"#/components/schemas/FootballActual"},{"type":"null"}],"description":"Realized result, present once the match has been played and reconciled. Null for upcoming / unplayed matches — the predictive fields above are identical either way."}},"type":"object","required":["match_id","league","prediction_date","season","home_team","away_team","probabilities"],"title":"FootballMatchDetail"},"FootballPerformance":{"properties":{"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"source":{"type":"string","enum":["production","test"],"title":"Source"},"league":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"League","description":"Customer-facing league code (e.g. 'serie_a') when the query narrowed to a single league; null for the cross-league aggregate."},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed matches aggregated into this snapshot."},"date_range":{"$ref":"#/components/schemas/DateRange"},"three_way_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Three Way Accuracy","description":"Accuracy of the home/draw/away (1X2) classifier."},"over_under_2_5_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Over Under 2 5 Accuracy","description":"Accuracy of the over/under 2.5 goals classifier."},"btts_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Btts Accuracy","description":"Accuracy of the both-teams-to-score classifier."}},"type":"object","required":["source","sample_size","date_range"],"title":"FootballPerformance"},"FootballProbabilities":{"properties":{"home_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Home Win","description":"Probability home team wins outright. Source: 1X2 classifier."},"draw":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Draw","description":"Probability of a draw. Source: 1X2 classifier (same model as home_win / away_win)."},"away_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Away Win","description":"Probability away team wins outright. Source: 1X2 classifier."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"Argmax of the 1X2 classifier (home / draw / away)."},"under_2_5":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Under 2 5","description":"P(total goals ≤ 2). Source: INDEPENDENT over/under classifier — NOT derived from the 1X2 model."},"over_2_5":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Over 2 5","description":"P(total goals ≥ 3). Source: INDEPENDENT over/under classifier — NOT derived from the 1X2 model."},"predicted_under_over":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Under Over"},"btts_yes":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Btts Yes","description":"P(both teams score ≥ 1). Source: INDEPENDENT BTTS classifier — NOT derived from the 1X2 or O/U models."},"btts_no":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Btts No"},"predicted_btts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Btts"}},"type":"object","title":"FootballProbabilities","description":"Three-way result + over/under + both-teams-to-score outcomes.\n\nNOTE — model independence: ``home_win``/``draw``/``away_win`` are the\noutput of the 1X2 softmax classifier; ``over_2_5``/``under_2_5`` are\nthe output of an independent over/under binary classifier; ``btts_yes``/\n``btts_no`` are the output of a third independent classifier. The three\nfamilies do NOT share a joint likelihood: a high P(home_win) need not\nimply a correspondingly high P(over_2_5), and the 1X2 marginal need\nnot equal the sum of any exact-score family. Apparent inconsistencies\nbetween these probabilities on the same match are EXPECTED BY DESIGN.\nTo assess the reliability of any single probability call\n``GET /v1/calibration/football/<model_id>`` (or its MCP equivalent\n``get_model_calibration``). Discover model ids via ``GET /v1/models``."},"FootballRatingHistoryPoint":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"Matchday the rating was computed for."},"team_name":{"type":"string","title":"Team Name"},"elo_overall_rating":{"type":"number","title":"Elo Overall Rating","description":"Overall ELO entering the matchday. Read with `rating_basis`: top-flight leagues reset to the 1500 baseline each season (season_relative); second divisions seed from the previous season, regressed toward the mean (carryover)."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played"},"elo_position":{"type":"integer","minimum":1.0,"title":"Elo Position","description":"League rank by overall ELO on that matchday."}},"type":"object","required":["date","team_name","elo_overall_rating","matches_played","elo_position"],"title":"FootballRatingHistoryPoint"},"FootballRatingsHistoryResponse":{"properties":{"season":{"type":"string","title":"Season"},"rating_basis":{"type":"string","enum":["season_relative","carryover"],"title":"Rating Basis","default":"season_relative"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Points in this page."},"has_more":{"type":"boolean","title":"Has More","description":"True when further pages exist — advance `offset`."},"license":{"type":"string","title":"License","description":"Ratings-data license. Reuse permitted with attribution: link to https://www.predixsport.com","default":"CC BY 4.0"},"attribution":{"type":"string","title":"Attribution","default":"Reuse permitted with attribution: link to https://www.predixsport.com"},"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"league":{"type":"string","title":"League","description":"Customer-facing league code (e.g. 'serie_a')."},"points":{"items":{"$ref":"#/components/schemas/FootballRatingHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["season","count","has_more","league","points"],"title":"FootballRatingsHistoryResponse"},"FootballRatingsResponse":{"properties":{"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"season":{"type":"string","title":"Season"},"snapshot_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Snapshot Date","description":"Date of the most recent completed match included in the ratings (the data horizon) — NOT when they were computed, see `run_date`."},"run_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Run Date","description":"Date the ratings were computed (the module run). With several leagues in one response, the first league's run."},"teams":{"items":{"$ref":"#/components/schemas/FootballTeamRating"},"type":"array","title":"Teams"}},"type":"object","required":["season","teams"],"title":"FootballRatingsResponse"},"FootballSearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"league":{"type":"string","title":"League"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"}},"type":"object","required":["match_id","league","prediction_date","season","home_team","away_team"],"title":"FootballSearchMatch"},"FootballSearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/FootballSearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["matches","count","has_more"],"title":"FootballSearchResponse"},"FootballSimulationsResponse":{"properties":{"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of league forecasts returned."},"leagues":{"items":{"$ref":"#/components/schemas/FootballLeagueSimulation"},"type":"array","title":"Leagues"}},"type":"object","required":["count","leagues"],"title":"FootballSimulationsResponse"},"FootballTeamForecast":{"properties":{"team":{"type":"string","title":"Team","description":"Club name (e.g. `Inter`, `Man City`)."},"played":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Played","description":"League matches played to date."},"points":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Points","description":"League points to date."},"wins":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Wins","description":"Wins to date."},"draws":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Draws","description":"Draws to date."},"losses":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Losses","description":"Losses to date."},"goal_diff":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Goal Diff","description":"Goal difference to date."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points","description":"Expected FINAL league points (simulation mean)."},"expected_goal_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Goal Diff","description":"Expected final goal difference (simulation mean)."},"mean_rank":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Rank","description":"Mean simulated final league position (1 = champion)."},"p_champion":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Champion","description":"Probability of winning the league title — the share of simulated seasons this club finished first."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of finishing in each table band, keyed by band name. Which bands are present depends on the league's format. Top flights: `champion`, `champions_league`, `europa_league`, `relegation`, plus `relegation_playoff` where the slot exists. Second divisions carry promotion bands instead: `champion`, `auto_promotion`, `playoff_zone`, `playoff_final`, `playoff_win`, `promoted`, `relegation`, `playout_zone`, `relegated_total`, `barrage_up`, `barrage_down` — again only those the league's format defines (e.g. {\"auto_promotion\": 0.41, \"playoff_win\": 0.12, \"relegation\": 0.0}). Band sizes are in the league-level `structure`."}},"type":"object","required":["team","p_champion"],"title":"FootballTeamForecast"},"FootballTeamRating":{"properties":{"name":{"type":"string","title":"Name"},"league":{"type":"string","title":"League","description":"Customer-facing league code (e.g. 'serie_a')."},"position_overall_elo":{"type":"integer","minimum":1.0,"title":"Position Overall Elo","description":"Rank within the league by overall ELO (1 = strongest)."},"elo_overall_rating":{"type":"number","title":"Elo Overall Rating","description":"Combined ELO rating across attack + defense. See /blog/football-elo-ratings for the methodology."},"elo_attack_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elo Attack Rating","description":"Attack-side component of the team's ELO. Updated more strongly by goal-scoring outcomes than by clean sheets."},"elo_defense_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elo Defense Rating","description":"Defense-side component of the team's ELO. Updated more strongly by goals-conceded outcomes than by goals scored."},"position_overall_glicko2":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Position Overall Glicko2","description":"Rank within the league by overall Glicko-2."},"glicko2_overall_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko2 Overall Rating","description":"Glicko-2 rating — adds Rating Deviation (uncertainty) and Volatility on top of ELO. See /blog/football-glicko2-ratings."},"glicko2_attack_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko2 Attack Rating","description":"Attack-side component of the team's Glicko-2 rating."},"glicko2_defense_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko2 Defense Rating","description":"Defense-side component of the team's Glicko-2 rating."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played"},"wins":{"type":"integer","minimum":0.0,"title":"Wins"},"draws":{"type":"integer","minimum":0.0,"title":"Draws"},"losses":{"type":"integer","minimum":0.0,"title":"Losses"},"goals_for":{"type":"integer","minimum":0.0,"title":"Goals For","description":"Total goals scored this season."},"goals_against":{"type":"integer","minimum":0.0,"title":"Goals Against","description":"Total goals conceded this season."},"league_points":{"type":"integer","minimum":0.0,"title":"League Points","description":"League-table points: 3 per win, 1 per draw, 0 per loss."}},"type":"object","required":["name","league","position_overall_elo","elo_overall_rating","matches_played","wins","draws","losses","goals_for","goals_against","league_points"],"title":"FootballTeamRating"},"FootballUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["football"],"const":"football","title":"Sport","default":"football"},"league":{"type":"string","title":"League","description":"Customer-facing league code. 12 leagues: 5 top flights (serie_a, premier_league, la_liga, bundesliga, ligue_1) and 7 second divisions (championship, league_one, league_two, segunda_division, ligue_2, bundesliga_2, serie_b)."},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"string","title":"Season"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"},"probability_home_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Home Win","description":"P(home win). Source: 1X2 softmax classifier."},"probability_draw":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Draw","description":"P(draw). Source: 1X2 softmax classifier (same as home_win / away_win)."},"probability_away_win":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Away Win","description":"P(away win). Source: 1X2 softmax classifier."},"probability_under_2_5":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Under 2 5","description":"P(total goals ≤ 2). Source: INDEPENDENT over/under classifier — NOT derived from the 1X2 model."},"probability_over_2_5":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Over 2 5","description":"P(total goals ≥ 3). Source: INDEPENDENT over/under classifier — NOT derived from the 1X2 model."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"Argmax of the 1X2 classifier (home / draw / away)."}},"type":"object","required":["match_id","league","prediction_date","season","home_team","away_team"],"title":"FootballUpcomingMatch","description":"One upcoming football match with the top-of-card predictions.\n\nThe three probability families exposed here — 1X2 (home_win / draw /\naway_win), over/under, and the implicit BTTS available via the\nmatch-detail endpoint — come from SEPARATE, INDEPENDENTLY-TRAINED\nclassifiers. They do not share a joint likelihood: a high P(home_win)\ndoes NOT mechanically imply a high P(over_2_5), and the 1X2 marginal\nis not constrained to equal the sum of any goal-spread family.\nCross-metric inconsistencies on the same match are by design — the\nright tool to interrogate them is ``GET /v1/calibration/football/<model_id>``\n(or ``get_model_calibration`` in MCP). Discover ids via\n``GET /v1/models?sport=football``."},"FootballUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/FootballUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"FootballUpcomingResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"KeyFactor":{"properties":{"key":{"type":"string","title":"Key","description":"Stable identifier of the factor (e.g. 'surface', 'starting_five', 'attack')."},"label":{"type":"string","title":"Label","description":"Human-readable factor name, as shown on predixsport.com."},"prob_points":{"type":"number","title":"Prob Points","description":"How many win-probability percentage points this factor moves the prediction. Positive values favour the first side (player one / home team), negative values the second side."},"direction":{"type":"string","title":"Direction","description":"What the factor favours: 'player_one'/'player_two' for tennis, 'home'/'away' for NBA and football winner attributions, 'under'/'over' for the football under/over 2.5 attribution."},"stable":{"type":"boolean","title":"Stable","description":"True when the attribution sign is consistent across independent estimator runs. Treat unstable factors as indicative, not decisive."},"display":{"type":"boolean","title":"Display","description":"False for internal context buckets not shown on predixsport.com.","default":true},"share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Share","description":"This factor's share of the total absolute attribution (0-1)."},"synthetic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Synthetic","description":"True for fixed baseline terms (home advantage, league scoring environment) that reflect league-wide tendencies rather than this specific matchup."},"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side","description":"For per-team factors on outcome attributions (football under/over): which team the factor describes ('home'/'away'). Surfaces can substitute the actual team name into the label."}},"type":"object","required":["key","label","prob_points","direction","stable"],"title":"KeyFactor","description":"One concept-level driver of the winner prediction."},"KeyFactors":{"properties":{"schema_version":{"type":"integer","title":"Schema Version","description":"Contract version of this payload.","default":1},"baseline_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline Probability","description":"Model probability for an average matchup — the reference point the factors move the prediction away from."},"factors":{"items":{"$ref":"#/components/schemas/KeyFactor"},"type":"array","title":"Factors","description":"Factors sorted by absolute impact (largest first)."}},"type":"object","title":"KeyFactors","description":"Concept-level attribution of the winner prediction.\n\nComputed with gradient-based attribution on the production neural\nnetwork and aggregated into human concepts; the factors approximately\nsum to the gap between the served win probability and an even matchup.\nFor football the factors compare the two sides directly (the draw\nprobability is modelled separately). Absent on rows predicted before\nthe explainability rollout (June 2026)."},"ModelInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Stable identifier, e.g. \"nba_winner\"."},"sport":{"type":"string","enum":["nba","euroleague","football","tennis","wta","champions"],"title":"Sport"},"task":{"type":"string","title":"Task","description":"One of: \"binary_classifier\", \"multiclass_classifier\", \"poisson_regressor\", \"negbin_regressor\"."},"target":{"type":"string","title":"Target","description":"Human-readable target description."},"classes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Classes","description":"Class labels (multiclass models only)."},"deployed_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deployed Since","description":"ISO date the model was first deployed to production."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"calibration_basename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calibration Basename","description":"Internal short name used to locate this model's calibration file on disk (``*_calibration_data_<basename>_deploy.json``). Customers normally don't need this — pass ``id`` to ``/v1/calibration/{sport}/{id}`` and the service resolves it. Exposed for transparency."}},"type":"object","required":["id","sport","task","target"],"title":"ModelInfo"},"ModelsResponse":{"properties":{"models":{"items":{"$ref":"#/components/schemas/ModelInfo"},"type":"array","title":"Models"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["models","count"],"title":"ModelsResponse"},"NBAActual":{"properties":{"home_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Points","description":"Points scored by the home team."},"away_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Away Points","description":"Points scored by the away team."},"point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Point Diff","description":"Realized (home_points - away_points). Compare with predicted_point_diff."},"total_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Points","description":"Realized combined points (home + away). Compare with predicted_total_points."},"result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result","description":"Which side won: 'Home_Win' or 'Away_Win'."}},"type":"object","title":"NBAActual","description":"Realized box-score result once the match has been played and\nreconciled (``in_season_performance``). Absent from the response until\nthe game is played — the predictive fields are unchanged either way."},"NBAFantasyBenchSeat":{"properties":{"player_name":{"type":"string","title":"Player Name"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"value_source":{"anyOf":[{"type":"string","enum":["model","weekly_rule","baseline"]},{"type":"null"}],"title":"Value Source"},"playing":{"type":"boolean","title":"Playing","default":true},"reason":{"type":"string","enum":["no_game","no_eligible_slot","outranked","baseline_only"],"title":"Reason"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"opponent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opponent"},"is_home":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Home"},"lineup_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lineup Status"},"positions":{"items":{"type":"string"},"type":"array","title":"Positions"},"eligibility_confirmed":{"type":"boolean","title":"Eligibility Confirmed","default":false}},"additionalProperties":false,"type":"object","required":["player_name","reason"],"title":"NBAFantasyBenchSeat"},"NBAFantasyBoard":{"properties":{"preset":{"type":"string","title":"Preset"},"playing_today":{"type":"integer","title":"Playing Today","default":0},"gameday":{"$ref":"#/components/schemas/NBAFantasyGameday"},"week":{"$ref":"#/components/schemas/NBAFantasyWeek"},"captain":{"$ref":"#/components/schemas/NBAFantasyCaptain"},"missing_from_slate":{"items":{"type":"string"},"type":"array","title":"Missing From Slate"},"early_season":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyEarlySeason"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["preset","gameday","week","captain"],"title":"NBAFantasyBoard"},"NBAFantasyBuiltSquad":{"properties":{"expected_points":{"type":"number","title":"Expected Points","description":"The five this squad would field on EVERY Gameday of the window, added up. The window is the first two official Gameweeks still ahead and the same ten are held for all of it."},"fielded_points":{"type":"number","title":"Fielded Points","description":"The same number, named for what it measures."},"proxy_points":{"type":"number","title":"Proxy Points","description":"The old linear ranking, over all ten. Kept for provenance and decides nothing."},"gameweeks":{"items":{"$ref":"#/components/schemas/NBAFantasyBuiltSquadGameweek"},"type":"array","title":"Gameweeks","description":"What each Gameweek of the window contributed, so a squad that gives up the first one to win the second is visible rather than averaged away."},"squad_value":{"type":"number","title":"Squad Value"},"bank_after":{"type":"number","title":"Bank After","description":"What is left of the cap after buying these ten, which is what the game prints. The reserve is named beside it and is part of this number, not subtracted from it twice."},"reserve":{"type":"number","title":"Reserve","description":"Millions the owner asked us to keep back.","default":0.0},"squad":{"items":{"$ref":"#/components/schemas/NBAFantasyBuiltSquadRow"},"type":"array","title":"Squad"}},"additionalProperties":false,"type":"object","required":["expected_points","fielded_points","proxy_points","squad_value","bank_after","squad"],"title":"NBAFantasyBuiltSquad"},"NBAFantasyBuiltSquadGameweek":{"properties":{"gameweek":{"type":"integer","title":"Gameweek"},"gamedays":{"type":"integer","title":"Gamedays"},"fielded_points":{"type":"number","title":"Fielded Points","description":"What this squad fields over that Gameweek alone."}},"additionalProperties":false,"type":"object","required":["gameweek","gamedays","fielded_points"],"title":"NBAFantasyBuiltSquadGameweek"},"NBAFantasyBuiltSquadRow":{"properties":{"player_name":{"type":"string","title":"Player Name"},"court":{"type":"string","enum":["BC","FC"],"title":"Court"},"team_code":{"type":"string","title":"Team Code"},"price":{"type":"number","title":"Price"},"expected_points":{"type":"number","title":"Expected Points"},"window_games":{"type":"integer","title":"Window Games","description":"How many Gamedays of the window his club plays. It is what makes a cheap tenth man legible: either he covers a night the five do not, or nothing cheaper was legal.","default":0},"valuation_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Valuation Basis","description":"season_price_blend_v1 blends what he did last season with what his price implies for his court. price_line_v1 is the price alone, which is all we have for a player with no season record."},"season_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Season Basis","description":"Which season the record behind this player's value came from. At the start of a season it is LAST season's, because nothing has been played in this one yet."}},"additionalProperties":false,"type":"object","required":["player_name","court","team_code","price","expected_points"],"title":"NBAFantasyBuiltSquadRow"},"NBAFantasyCaptain":{"properties":{"policy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy","description":"Identifier of the captain policy that produced this answer. 'captain_form_daily_v1' is the measured decision-3b rule."},"state":{"type":"string","title":"State","description":"play, wait, wait_lineup, no_form or chip_used. The rule has no model in it: it backs the squad's best realised-form player OF THOSE WITH A GAME LEFT in the Gameweek, on the first day he is in his team's published five, and it forces the choice on the last day the squad can still spend the chip. wait_lineup is that last day with no published five yet."},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"form":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Form"},"form_games":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Form Games"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"expected_today":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Today"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why today is not the day: no_game, no_lineup, not_in_five, or no_games_left when the whole squad is out of Gameweek games."},"next_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Day"},"chip_used_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chip Used On"},"forced":{"type":"boolean","title":"Forced","description":"The Gameweek ends for this squad today, so the call is the forced one rather than a choice between days.","default":false},"last_usable_day":{"type":"boolean","title":"Last Usable Day","description":"No rostered player has a game later in the Gameweek and the whole window is inside the projection horizon.","default":false},"gameweek":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyGameweek"},{"type":"null"}]},"gameweek_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gameweek Source","description":"Same value as gameweek.source, flat for convenience."},"ranked":{"items":{"$ref":"#/components/schemas/NBAFantasyCaptainRanked"},"type":"array","title":"Ranked"},"candidates":{"items":{"type":"string"},"type":"array","title":"Candidates","description":"Rostered players with a game left in the Gameweek."},"no_games_left":{"items":{"type":"string"},"type":"array","title":"No Games Left","description":"Rostered players with form whose Gameweek games are all played. They are ranked and named but cannot be captained in it."},"without_form":{"items":{"type":"string"},"type":"array","title":"Without Form"},"skipped":{"items":{"type":"string"},"type":"array","title":"Skipped"}},"additionalProperties":false,"type":"object","required":["state"],"title":"NBAFantasyCaptain"},"NBAFantasyCaptainDay":{"properties":{"date":{"type":"string","title":"Date"},"players":{"items":{"$ref":"#/components/schemas/NBAFantasyCaptainDayPlayer"},"type":"array","title":"Players"}},"additionalProperties":false,"type":"object","required":["date","players"],"title":"NBAFantasyCaptainDay"},"NBAFantasyCaptainDayPlayer":{"properties":{"player_name":{"type":"string","title":"Player Name"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"expected":{"type":"number","title":"Expected"}},"additionalProperties":false,"type":"object","required":["player_name","expected"],"title":"NBAFantasyCaptainDayPlayer"},"NBAFantasyCaptainRanked":{"properties":{"player_name":{"type":"string","title":"Player Name"},"form":{"type":"number","title":"Form"},"games_left":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Games Left","description":"Games this player has left in the Gameweek, today included. Zero means the chip can no longer be spent on him."}},"additionalProperties":false,"type":"object","required":["player_name","form"],"title":"NBAFantasyCaptainRanked"},"NBAFantasyCategory":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"lower_is_better":{"type":"boolean","title":"Lower Is Better","default":false},"ratio_of_means":{"type":"boolean","title":"Ratio Of Means","description":"True for FG% and FT%, which are the ratio of two projected averages rather than a projected percentage. The distinction matters: the expected ratio is not the ratio of expectations.","default":false}},"type":"object","required":["key","label"],"title":"NBAFantasyCategory","description":"One Yahoo 9-category value for a player."},"NBAFantasyCloseCall":{"properties":{"slot":{"type":"string","title":"Slot"},"seated":{"type":"string","title":"Seated"},"alternative":{"type":"string","title":"Alternative"},"gap":{"type":"number","title":"Gap"}},"additionalProperties":false,"type":"object","required":["slot","seated","alternative","gap"],"title":"NBAFantasyCloseCall"},"NBAFantasyEarlySeason":{"properties":{"active":{"type":"boolean","title":"Active","description":"True in the opening fortnight of a season, before the player model can run: tonight's numbers then come from an audited early-season prior (last season, this season's first games and tonight's role), and/or the Captain ranks on last season's form.","default":false},"model_variants":{"items":{"type":"string"},"type":"array","title":"Model Variants","description":"Labels of the early-season rows on the board, e.g. cold_start_player_prior_nba_v1."},"form_source":{"type":"string","enum":["current_season","previous_season"],"title":"Form Source","description":"Which season's realised form the Captain rule ranks on.","default":"current_season"}},"additionalProperties":false,"type":"object","title":"NBAFantasyEarlySeason"},"NBAFantasyEligibility":{"properties":{"positions":{"items":{"type":"string","enum":["PG","SG","SF","PF","C"]},"type":"array","maxItems":5,"minItems":1,"title":"Positions"},"confirmed":{"type":"boolean","title":"Confirmed","description":"False while the eligibility is OUR guess from a listed position. Every surface that places the player says so until it is true.","default":false}},"additionalProperties":false,"type":"object","required":["positions"],"title":"NBAFantasyEligibility"},"NBAFantasyGameday":{"properties":{"ok":{"type":"boolean","title":"Ok"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"formation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Formation"},"starters":{"items":{"$ref":"#/components/schemas/NBAFantasyGamedayPlayer"},"type":"array","title":"Starters"},"expected_total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Total"},"bench":{"items":{"$ref":"#/components/schemas/NBAFantasyGamedayPlayer"},"type":"array","title":"Bench"},"unassigned":{"items":{"type":"string"},"type":"array","title":"Unassigned"},"playing":{"type":"integer","title":"Playing","default":0},"legal_fives":{"type":"integer","title":"Legal Fives","default":0},"forced":{"type":"boolean","title":"Forced","description":"The squad admits exactly one legal five, so the card is a display of a decision already made rather than a choice.","default":false},"captain_candidates":{"items":{"$ref":"#/components/schemas/NBAFantasyCaptainDay"},"type":"array","title":"Captain Candidates"},"fillers":{"items":{"type":"string"},"type":"array","title":"Fillers","description":"Starters with no game tonight, named so the five is legal: fewer than five of the squad play, and the game still wants five."}},"additionalProperties":false,"type":"object","required":["ok"],"title":"NBAFantasyGameday"},"NBAFantasyGamedayPlayer":{"properties":{"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"court":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Court"},"expected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected"},"lineup_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lineup Status"},"low_confidence":{"type":"boolean","title":"Low Confidence","default":false},"filler":{"type":"boolean","title":"Filler","description":"No game tonight: fielded only to complete a legal five when the players with a game cannot, so he scores zero.","default":false}},"additionalProperties":false,"type":"object","title":"NBAFantasyGamedayPlayer"},"NBAFantasyGameweek":{"properties":{"source":{"type":"string","enum":["official","iso_week_approximation"],"title":"Source","description":"Where the Gameweek boundary came from. 'official' is a hand-entered calendar of the game's own Gameweeks. 'iso_week_approximation' is the ISO week (Monday to Sunday) of the slate day in US Eastern, which is an approximation of the boundary and NOT an official Gameweek."},"gameweek":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gameweek","description":"Official Gameweek number, null under the approximation."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"},"end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End","description":"Last day of the window, the day the chip expires."},"last_gameday":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Gameday"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"}},"additionalProperties":false,"type":"object","required":["source"],"title":"NBAFantasyGameweek"},"NBAFantasyGameweekWindow":{"properties":{"source":{"anyOf":[{"type":"string","enum":["official","iso_week_approximation"]},{"type":"null"}],"title":"Source","description":"'official' is the game's own Gameweek calendar. 'iso_week_approximation' is the ISO week of the slate day and is NOT an official Gameweek: say so wherever the window is shown."},"gameweek":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gameweek"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"},"end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"},"today":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Today"},"days_counted":{"items":{"type":"string"},"type":"array","title":"Days Counted","description":"The days inside the window our published calendar can see."},"players_with_games":{"type":"integer","minimum":0.0,"title":"Players With Games","description":"Priced players with at least one game left in the window. Zero means there is nothing left to plan for in this Gameweek, which is a state and not a failure.","default":0}},"additionalProperties":false,"type":"object","title":"NBAFantasyGameweekWindow","description":"The Gameweek a transfer plan was made for, and how we know it."},"NBAFantasyLeader":{"properties":{"rank":{"type":"integer","minimum":1.0,"title":"Rank"},"player_name":{"type":"string","title":"Player Name"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"games":{"type":"integer","minimum":1.0,"title":"Games","description":"Games he has left in the window."},"days":{"items":{"type":"string"},"type":"array","title":"Days","description":"The American dates of those games."},"expected":{"type":"number","title":"Expected","description":"Projected NBA Salary Cap fantasy points over those games: a sum of per-game expectations, no band, no probability."},"per_game":{"type":"number","title":"Per Game"},"prior":{"type":"boolean","title":"Prior","description":"True when at least one of his games is projected by the early-season prior rather than the model's own rows."}},"additionalProperties":false,"type":"object","required":["rank","player_name","games","days","expected","per_game","prior"],"title":"NBAFantasyLeader","description":"One player in the league-wide Gameweek ranking."},"NBAFantasyLeaderCourts":{"properties":{"BC":{"items":{"$ref":"#/components/schemas/NBAFantasyLeader"},"type":"array","title":"Bc","description":"Backcourt, best first."},"FC":{"items":{"$ref":"#/components/schemas/NBAFantasyLeader"},"type":"array","title":"Fc","description":"Frontcourt, best first."}},"additionalProperties":false,"type":"object","required":["BC","FC"],"title":"NBAFantasyLeaderCourts"},"NBAFantasyLeadersResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"season":{"type":"string","title":"Season"},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"Day of the projection batch read, US date."},"per_court":{"type":"integer","title":"Per Court"},"gameweek":{"$ref":"#/components/schemas/NBAFantasyGameweek"},"window":{"$ref":"#/components/schemas/NBAFantasyLeadersWindow"},"basis":{"anyOf":[{"type":"string","enum":["model","prior","mixed"]},{"type":"null"}],"title":"Basis","description":"'model' when every game in the window is projected by the model, 'prior' when every one comes from the early-season prior, 'mixed' otherwise, null when the window holds no game."},"courts":{"$ref":"#/components/schemas/NBAFantasyLeaderCourts"}},"additionalProperties":false,"type":"object","required":["season","per_court","gameweek","window","courts"],"title":"NBAFantasyLeadersResponse"},"NBAFantasyLeadersWindow":{"properties":{"start":{"type":"string","title":"Start"},"end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"}},"additionalProperties":false,"type":"object","required":["start"],"title":"NBAFantasyLeadersWindow"},"NBAFantasyLeagueBlock":{"properties":{"rules":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Rules"},"optimisable":{"type":"boolean","title":"Optimisable","description":"False for a nine-category league, which has no single score and therefore no lineup optimiser and no add/drop comparison."},"slate_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slate Day"},"horizon":{"items":{"type":"string"},"type":"array","title":"Horizon"},"horizon_end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horizon End Reason"},"unconfirmed_eligibility":{"items":{"type":"string"},"type":"array","title":"Unconfirmed Eligibility","description":"Players whose platform eligibility is still our guess from their listed position."},"lock_slots":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Lock Slots","description":"Which of the league's slots each player may be locked into. A lock pins the slot he is already playing in, so this is the list that slot is chosen from."},"board_digest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Digest"},"snapshots":{"additionalProperties":{"$ref":"#/components/schemas/NBAFantasySnapshotSummary"},"type":"object","title":"Snapshots"}},"additionalProperties":false,"type":"object","required":["optimisable"],"title":"NBAFantasyLeagueBlock","description":"The season-long half of the owner read."},"NBAFantasyLeagueRules":{"properties":{"version":{"type":"integer","title":"Version","default":1},"preset":{"anyOf":[{"type":"string","enum":["espn_points","yahoo_9cat","sleeper_points","nba_salary_cap"]},{"type":"null"}],"title":"Preset"},"slots":{"anyOf":[{"items":{"$ref":"#/components/schemas/NBAFantasySlot"},"type":"array","maxItems":14},{"type":"null"}],"title":"Slots","description":"Omit for the ESPN default: PG, SG, SF, PF, C, G, F and three UTIL."},"bench":{"type":"integer","maximum":30.0,"minimum":0.0,"title":"Bench","default":5},"matchup_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Matchup End","description":"Last day of the current matchup, in the owner's own time zone."},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone"},"lock":{"type":"string","enum":["per_game"],"const":"per_game","title":"Lock","description":"The only lineup lock served today. Weekly-lock leagues, waivers and acquisition limits are out of scope.","default":"per_game"},"eligibility":{"additionalProperties":{"$ref":"#/components/schemas/NBAFantasyEligibility"},"type":"object","maxProperties":40,"title":"Eligibility"}},"additionalProperties":false,"type":"object","title":"NBAFantasyLeagueRules","description":"The contract a season-long league is played under.\n\nNo two season-long leagues are the same: the active slots, what a\nplatform lets a player fill and when the matchup ends all differ, and a\nlineup computed from a default would be right for one owner and wrong\nfor the next with nothing in the response to say so."},"NBAFantasyLineupRequest":{"properties":{"season":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"title":"Season","description":"NBA season, e.g. 2027. Omitted = the current season."},"locked":{"items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/NBAFantasyLock"}]},"type":"array","maxItems":15,"title":"Locked","description":"Players whose game has already tipped off. A lock keeps a player in the slot he is ALREADY playing in tonight, so each entry carries that slot: {\"player_name\": \"...\", \"slot\": \"UTIL\"}. A bare name is still accepted and is resolved against the lineup last returned for the same day; with no such lineup the request is refused rather than guessed at."}},"additionalProperties":false,"type":"object","title":"NBAFantasyLineupRequest"},"NBAFantasyLineupResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"mode":{"type":"string","enum":["lineup"],"const":"lineup","title":"Mode","default":"lineup"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"preset":{"type":"string","title":"Preset"},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Day"},"board_digest":{"type":"string","title":"Board Digest"},"roster_revision":{"type":"integer","title":"Roster Revision"},"slots":{"items":{"$ref":"#/components/schemas/NBAFantasySlot"},"type":"array","title":"Slots"},"assignments":{"items":{"$ref":"#/components/schemas/NBAFantasySeat"},"type":"array","title":"Assignments"},"bench":{"items":{"$ref":"#/components/schemas/NBAFantasyBenchSeat"},"type":"array","title":"Bench"},"unfilled":{"items":{"type":"string"},"type":"array","title":"Unfilled","description":"Slots no player you hold is eligible to fill tonight."},"expected_total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Total"},"close_calls":{"items":{"$ref":"#/components/schemas/NBAFantasyCloseCall"},"type":"array","title":"Close Calls"},"locked_unplaced":{"items":{"type":"string"},"type":"array","title":"Locked Unplaced"},"baseline":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline","description":"What a player we hold no projection for was priced at, read off the bottom of this squad. Never zero."}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","preset","board_digest","roster_revision"],"title":"NBAFantasyLineupResponse"},"NBAFantasyLock":{"properties":{"player_name":{"type":"string","maxLength":128,"minLength":1,"title":"Player Name"},"slot":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Slot","description":"The slot he is ALREADY playing in, one of the league's own. Omitted, it is read from the lineup last returned for the same day."}},"additionalProperties":false,"type":"object","required":["player_name"],"title":"NBAFantasyLock","description":"One player whose game has tipped off, and the seat he is in.\n\nThe slot is the point of it. A lock that forced presence alone let the\noptimiser move a guard out of UTIL into PG to free UTIL for a center,\nwhich is not a lineup anybody can submit after tip-off."},"NBAFantasyMarketReference":{"properties":{"snapshot_id":{"type":"integer","title":"Snapshot Id"},"revision":{"type":"integer","title":"Revision"},"period_kind":{"type":"string","enum":["gameday","calendar_day"],"title":"Period Kind"},"period_key":{"type":"string","title":"Period Key"},"source_observed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Source Observed At"},"collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Collected At"},"deadline_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deadline At","description":"The official Gameday deadline this market was frozen before."},"player_count":{"type":"integer","minimum":0.0,"title":"Player Count"},"priced_players":{"type":"integer","minimum":0.0,"title":"Priced Players","description":"How many priced players we could place against our own player identity. The rest are on the market and not in this plan."},"duplicate_identities":{"type":"integer","minimum":0.0,"title":"Duplicate Identities","default":0}},"additionalProperties":false,"type":"object","required":["snapshot_id","revision","period_kind","period_key","player_count","priced_players"],"title":"NBAFantasyMarketReference","description":"Which frozen snapshot of the official market a plan was priced from."},"NBAFantasyPresetEnum":{"type":"string","enum":["espn_points","yahoo_9cat","sleeper_points","nba_salary_cap"],"title":"NBAFantasyPresetEnum"},"NBAFantasyRosterPlayer":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"court":{"anyOf":[{"type":"string","enum":["BC","FC"]},{"type":"null"}],"title":"Court","description":"Owner override of the listed position: BC for backcourt, FC for frontcourt. Null means the listed position decides."}},"additionalProperties":false,"type":"object","required":["name"],"title":"NBAFantasyRosterPlayer"},"NBAFantasyRosterWrite":{"properties":{"season":{"type":"string","pattern":"^20\\d{2}$","title":"Season"},"preset":{"type":"string","enum":["espn_points","yahoo_9cat","sleeper_points","nba_salary_cap"],"title":"Preset","description":"The scoring system this squad is played in. Omitted on an update = the saved one is kept (nba_salary_cap for a first save).","default":"nba_salary_cap"},"mode":{"anyOf":[{"type":"string","enum":["salary_cap","season_long"]},{"type":"null"}],"title":"Mode","description":"Which game this squad is played in: 'salary_cap' is the official NBA game, 'season_long' an ESPN-like points league. Omitted on an update = the saved one is kept; on a first save the scoring system decides."},"roster":{"items":{"$ref":"#/components/schemas/NBAFantasyRosterPlayer"},"type":"array","maxItems":15,"title":"Roster"},"league_rules":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyLeagueRules"},{"type":"null"}],"description":"The season-long league contract, versioned with the squad. Ignored on the Salary Cap side, which has one published rulebook. Omitted on an update = the saved contract is kept while the game and the scoring stay the same."},"chip_used_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Chip Used On","description":"The day the owner spent the Captain chip this Gameweek. Null means it is still available. Omitted on an update = the saved day is kept, so resending the squad never revives a spent chip."}},"additionalProperties":false,"type":"object","required":["season"],"title":"NBAFantasyRosterWrite"},"NBAFantasySalaryCapBlock":{"properties":{"available":{"type":"boolean","title":"Available","description":"False when there is no OPEN market to plan against, which is the steady state between two seasons and before the first collection of a period. The saved squad, tonight's card and the Captain call do not depend on it and keep working."},"state":{"type":"string","enum":["open","waiting_for_prices","no_market","market_unavailable","no_rules"],"title":"State","description":"'open' is a live Gameday market. 'waiting_for_prices' is the game between two seasons: a snapshot exists but its period is a calendar day rather than an open Gameday, so it is the board the game CLOSED the last season with. Nothing is priced on it, because it still names players who have retired since.","default":"no_market"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"The sentence to show the reader while there is no open market. Present for waiting_for_prices and no_market."},"free_transfers":{"type":"integer","minimum":0.0,"title":"Free Transfers"},"extra_transfer_point_cost":{"type":"integer","minimum":0.0,"title":"Extra Transfer Point Cost"},"transfer_rules_source":{"type":"string","title":"Transfer Rules Source"},"value_wording":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value Wording"},"court_source":{"type":"string","enum":["official_market"],"const":"official_market","title":"Court Source","default":"official_market"},"cap":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cap"},"squad_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Squad Size"},"max_per_club":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Per Club"},"market":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyMarketReference"},{"type":"null"}]},"window":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyGameweekWindow"},{"type":"null"}]},"squad":{"items":{"type":"object"},"type":"array","title":"Squad","description":"Your saved players at the frozen prices, with the game's court."},"squad_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Squad Value"},"unpriced":{"items":{"type":"string"},"type":"array","title":"Unpriced","description":"Saved players the frozen market carries no price for."},"board_digest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Digest"},"snapshots":{"additionalProperties":{"$ref":"#/components/schemas/NBAFantasySnapshotSummary"},"type":"object","title":"Snapshots"}},"additionalProperties":false,"type":"object","required":["available","free_transfers","extra_transfer_point_cost","transfer_rules_source"],"title":"NBAFantasySalaryCapBlock","description":"The official half of the owner read: prices, window, saved plans."},"NBAFantasySavedRoster":{"properties":{"season":{"type":"string","pattern":"^20\\d{2}$","title":"Season"},"preset":{"type":"string","enum":["espn_points","yahoo_9cat","sleeper_points","nba_salary_cap"],"title":"Preset","description":"The scoring system this squad is played in. Omitted on an update = the saved one is kept (nba_salary_cap for a first save).","default":"nba_salary_cap"},"mode":{"anyOf":[{"type":"string","enum":["salary_cap","season_long"]},{"type":"null"}],"title":"Mode","description":"Which game this squad is played in: 'salary_cap' is the official NBA game, 'season_long' an ESPN-like points league. Omitted on an update = the saved one is kept; on a first save the scoring system decides."},"roster":{"items":{"$ref":"#/components/schemas/NBAFantasyRosterPlayer"},"type":"array","maxItems":15,"title":"Roster"},"league_rules":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyLeagueRules"},{"type":"null"}],"description":"The season-long league contract, versioned with the squad. Ignored on the Salary Cap side, which has one published rulebook. Omitted on an update = the saved contract is kept while the game and the scoring stay the same."},"chip_used_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Chip Used On","description":"The day the owner spent the Captain chip this Gameweek. Null means it is still available. Omitted on an update = the saved day is kept, so resending the squad never revives a spent chip."},"revision":{"type":"integer","title":"Revision","description":"Increments only when the saved content changes, so an identical save is idempotent and does not move it."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Changed","description":"Whether this write changed the stored squad."}},"additionalProperties":false,"type":"object","required":["season","revision"],"title":"NBAFantasySavedRoster"},"NBAFantasyScenarioDay":{"properties":{"day":{"type":"string","title":"Day"},"keep_value":{"type":"number","title":"Keep Value"},"move_value":{"type":"number","title":"Move Value"},"incoming_contribution":{"type":"number","title":"Incoming Contribution","description":"What the incoming player adds INSIDE your slots that day. Zero on a day your slots at his positions were already full, which is why this compares lineups and not two players' totals."},"incoming_plays":{"type":"boolean","title":"Incoming Plays"},"outgoing_plays":{"type":"boolean","title":"Outgoing Plays"}},"additionalProperties":false,"type":"object","required":["day","keep_value","move_value","incoming_contribution","incoming_plays","outgoing_plays"],"title":"NBAFantasyScenarioDay"},"NBAFantasyScenarioMove":{"properties":{"incoming":{"type":"string","title":"Incoming"},"outgoing":{"type":"string","title":"Outgoing"},"incoming_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incoming Team"},"outgoing_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Outgoing Team"},"total":{"type":"number","title":"Total"},"delta":{"type":"number","title":"Delta","description":"Points against keeping the squad, over the horizon."},"incoming_games":{"type":"integer","title":"Incoming Games"},"outgoing_games":{"type":"integer","title":"Outgoing Games"},"protected":{"type":"boolean","title":"Protected","description":"The outgoing player is one of the squad's three best by what he has actually done this season.","default":false},"protected_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Protected Reason","description":"Present whenever protected is true, and never to be dropped: it is what stops a short-horizon number reading as advice to sell a star."},"days":{"items":{"$ref":"#/components/schemas/NBAFantasyScenarioDay"},"type":"array","title":"Days"}},"additionalProperties":false,"type":"object","required":["incoming","outgoing","total","delta","incoming_games","outgoing_games"],"title":"NBAFantasyScenarioMove"},"NBAFantasyScenarioRequest":{"properties":{"season":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"title":"Season"},"candidates":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Candidates","description":"The free agents you are weighing, by name. Resolved against the same season player index the roster uses, so an ambiguous surname is refused rather than guessed."},"droppable":{"items":{"type":"string"},"type":"array","maxItems":15,"title":"Droppable","description":"Players you would let go. Empty compares against every player you hold."}},"additionalProperties":false,"type":"object","required":["candidates"],"title":"NBAFantasyScenarioRequest"},"NBAFantasyScenarioResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"mode":{"type":"string","enum":["scenario"],"const":"scenario","title":"Mode","default":"scenario"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"preset":{"type":"string","title":"Preset"},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"board_digest":{"type":"string","title":"Board Digest"},"roster_revision":{"type":"integer","title":"Roster Revision"},"horizon":{"items":{"type":"string"},"type":"array","title":"Horizon"},"horizon_end_reason":{"anyOf":[{"type":"string","enum":["matchup_end","projection_horizon","no_projections"]},{"type":"null"}],"title":"Horizon End Reason","description":"Why the comparison stops where it does: your matchup ends, or our projections do. They are different facts."},"matchup_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matchup End"},"keep_total":{"type":"number","title":"Keep Total"},"keep_days":{"items":{"items":{},"type":"array"},"type":"array","title":"Keep Days"},"moves":{"items":{"$ref":"#/components/schemas/NBAFantasyScenarioMove"},"type":"array","title":"Moves"},"protected":{"items":{"type":"string"},"type":"array","title":"Protected"},"candidates":{"items":{"type":"string"},"type":"array","title":"Candidates"},"baseline":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline"},"value_sources":{"additionalProperties":{"type":"integer"},"type":"object","title":"Value Sources","description":"How many day values came from each source."}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","preset","board_digest","roster_revision","keep_total"],"title":"NBAFantasyScenarioResponse"},"NBAFantasyScore":{"properties":{"preset":{"type":"string","title":"Preset"},"label":{"type":"string","title":"Label"},"expectation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expectation","description":"The projected score for this preset. For nba_salary_cap it is the model trained on that total itself, which is the number every Predixsport surface prints and ranks by. For every other points preset it is the preset's scoring formula applied to the component projections, exact for the average by linearity whatever the dependence between the individual stats. Null when any stat the formula needs is missing for this player: a partial total would understate him with nothing here to say so. Always null for yahoo_9cat, which is a per-category league with no single score."},"expectation_source":{"type":"string","title":"Expectation Source","description":"Which of the two readings ``expectation`` holds: ``direct_model`` for a preset scored by a model trained on its own total (nba_salary_cap), ``component_formula`` for the linear combination of the component projections, ``none`` when no score could be formed.","default":"none"},"formula_expectation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Formula Expectation","description":"The preset formula applied to the component projections, published alongside ``expectation`` for nba_salary_cap so the two readings of the same total can be compared. It is a cross-check, not a second score: the served number is ``expectation``. Null for every other preset, where the two would be the same value."},"distribution_quantiles":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Distribution Quantiles","description":"Quantiles p10/p25/p50/p75/p90 of the score. Present for nba_salary_cap only, because it is the one preset with a model trained on the total itself. No composite distribution is claimed for the others: a sum of dependent counts is not the convolution of the marginals."},"categories":{"anyOf":[{"items":{"$ref":"#/components/schemas/NBAFantasyCategory"},"type":"array"},{"type":"null"}],"title":"Categories","description":"Per-category values. Present for yahoo_9cat only."}},"type":"object","required":["preset","label"],"title":"NBAFantasyScore","description":"Projected fantasy score for the requested scoring preset."},"NBAFantasySeat":{"properties":{"slot":{"type":"string","title":"Slot"},"seat":{"type":"integer","title":"Seat"},"player_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Player Name"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"value_source":{"anyOf":[{"type":"string","enum":["model","weekly_rule","baseline"]},{"type":"null"}],"title":"Value Source"},"locked":{"type":"boolean","title":"Locked","default":false},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"opponent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opponent"},"is_home":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Home"},"lineup_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lineup Status"},"positions":{"items":{"type":"string"},"type":"array","title":"Positions"},"eligibility_confirmed":{"type":"boolean","title":"Eligibility Confirmed","description":"False while this player's platform eligibility is OUR guess from his listed position. Say so wherever the placement is shown.","default":false},"playing":{"type":"boolean","title":"Playing","default":true}},"additionalProperties":false,"type":"object","required":["slot","seat"],"title":"NBAFantasySeat"},"NBAFantasySlot":{"properties":{"slot":{"type":"string","maxLength":16,"minLength":1,"title":"Slot"},"count":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Count"},"eligible":{"anyOf":[{"items":{"type":"string","enum":["PG","SG","SF","PF","C"]},"type":"array"},{"type":"null"}],"title":"Eligible","description":"Which positions this slot accepts. Omitted for the slots every platform names the same way (PG, SG, SF, PF, C, G, F, UTIL)."}},"additionalProperties":false,"type":"object","required":["slot","count"],"title":"NBAFantasySlot"},"NBAFantasySnapshotSummary":{"properties":{"id":{"type":"integer","title":"Id"},"mode":{"type":"string","enum":["lineup","scenario","transfers","squad_builder"],"title":"Mode"},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"board_digest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Digest"},"roster_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Roster Revision"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"stale":{"type":"boolean","title":"Stale","default":false},"stale_reasons":{"items":{"type":"string"},"type":"array","title":"Stale Reasons","description":"roster_changed, projections_changed, players_withheld (a player in it has since objected and was removed from it), or several. A stale plan is history, not advice: run it again."},"recommendation":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Recommendation"}},"additionalProperties":false,"type":"object","required":["id","mode"],"title":"NBAFantasySnapshotSummary","description":"A saved plan, and whether it still describes the squad on screen."},"NBAFantasySquadRequest":{"properties":{"season":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"title":"Season"},"bank_to_leave":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Bank To Leave","description":"Millions to keep back for later moves. Zero spends it all.","default":0.0},"protected":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Protected","description":"Players the squad must contain whatever the numbers say."},"excluded":{"items":{"type":"string"},"type":"array","maxItems":40,"title":"Excluded","description":"Players to leave out."}},"additionalProperties":false,"type":"object","title":"NBAFantasySquadRequest"},"NBAFantasySquadResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"mode":{"type":"string","enum":["squad_builder"],"const":"squad_builder","title":"Mode","default":"squad_builder"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"preset":{"type":"string","title":"Preset"},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion"},"court_source":{"type":"string","enum":["official_market"],"const":"official_market","title":"Court Source","default":"official_market"},"board_digest":{"type":"string","title":"Board Digest"},"roster_revision":{"type":"integer","title":"Roster Revision"},"market":{"$ref":"#/components/schemas/NBAFantasyMarketReference"},"budget":{"type":"number","title":"Budget"},"bank_to_leave":{"type":"number","title":"Bank To Leave"},"cap":{"type":"number","title":"Cap"},"value_basis":{"type":"string","enum":["fielded_five"],"const":"fielded_five","title":"Value Basis","description":"Squads are ranked on the five they would field, never on a sum of the ten.","default":"fielded_five"},"search":{"type":"string","enum":["exact","certified","truncated"],"title":"Search","description":"'exact' means every legal squad the budget reaches was compared and these are the best there are. 'certified' means the search stopped as soon as it could prove it was inside its optimality tolerance, and `search_gap` says how close to the best squad there is every squad here was proved to be. 'truncated' means the search ran out of time before it could prove anything, so these are the best reached and not the best there are. On anything but 'exact', say so beside the squads and say how many squads came back.","default":"exact"},"search_gap":{"type":"number","minimum":0.0,"title":"Search Gap","description":"How close to the best squad there is every squad here was PROVED to be, as a fraction: 0.004 is four tenths of a percent. Zero on an exact search.","default":0.0},"search_gap_tolerance":{"type":"number","minimum":0.0,"title":"Search Gap Tolerance","description":"The optimality gap the search was allowed to stop at. The builder takes one because the exact search over two Gameweeks of a full market is slow enough to time out, and a proved half a percent is a better answer than an apology.","default":0.0},"window":{"$ref":"#/components/schemas/NBAFantasySquadWindow"},"window_days":{"type":"integer","title":"Window Days","description":"How many Gamedays the squads were valued over."},"window_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Window Label","description":"The Gameweeks of the window, in words."},"window_assumption":{"type":"string","title":"Window Assumption","description":"The assumption behind every total here, to repeat beside it: the ten are held for the whole window and no future transfer is simulated inside it."},"protected":{"items":{"type":"string"},"type":"array","title":"Protected"},"excluded":{"items":{"type":"string"},"type":"array","title":"Excluded"},"recommended":{"$ref":"#/components/schemas/NBAFantasyBuiltSquad"},"alternatives":{"items":{"$ref":"#/components/schemas/NBAFantasyBuiltSquad"},"type":"array","title":"Alternatives"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","preset","board_digest","roster_revision","market","budget","bank_to_leave","cap","window","window_days","window_assumption","recommended"],"title":"NBAFantasySquadResponse"},"NBAFantasySquadRow":{"properties":{"player_name":{"type":"string","title":"Player Name"},"court":{"type":"string","enum":["BC","FC"],"title":"Court"},"team_code":{"type":"string","title":"Team Code"},"price":{"type":"number","title":"Price"},"expected_points":{"type":"number","title":"Expected Points"},"games_left":{"type":"integer","minimum":0.0,"title":"Games Left"},"status":{"type":"string","enum":["rule_valued","no_form_record","no_games_left","not_transactable"],"title":"Status"}},"additionalProperties":false,"type":"object","required":["player_name","court","team_code","price","expected_points","games_left","status"],"title":"NBAFantasySquadRow"},"NBAFantasySquadWindow":{"properties":{"days":{"items":{"type":"string"},"type":"array","title":"Days"},"start":{"type":"string","title":"Start"},"end":{"type":"string","title":"End"},"gameweeks":{"items":{"$ref":"#/components/schemas/NBAFantasySquadWindowWeek"},"type":"array","title":"Gameweeks"}},"additionalProperties":false,"type":"object","required":["days","start","end","gameweeks"],"title":"NBAFantasySquadWindow","description":"The Gamedays a squad built from scratch was valued over.\n\nThe first two official Gameweeks still ahead, from the Gameday calendar\nand the club games frozen with the market. Two Gameweeks is a product\ndefault and not a measured optimum."},"NBAFantasySquadWindowWeek":{"properties":{"gameweek":{"type":"integer","title":"Gameweek"},"days":{"items":{"type":"string"},"type":"array","title":"Days"}},"additionalProperties":false,"type":"object","required":["gameweek","days"],"title":"NBAFantasySquadWindowWeek"},"NBAFantasyStateResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"roster":{"$ref":"#/components/schemas/NBAFantasySavedRoster"},"board":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyBoard"},{"type":"null"}]},"salary_cap":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasySalaryCapBlock"},{"type":"null"}],"description":"Present on an official Salary Cap squad only: the frozen market it can be planned against, the Gameweek window, and the latest saved transfer plan and built squad."},"league":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyLeagueBlock"},{"type":"null"}],"description":"Present on a season-long squad only: the league contract, the days a plan can see and the latest saved lineup and add/drop plan with a straight answer about whether each still applies."}},"additionalProperties":false,"type":"object","required":["roster"],"title":"NBAFantasyStateResponse"},"NBAFantasyTransferMove":{"properties":{"player_name":{"type":"string","title":"Player Name"},"team_code":{"type":"string","title":"Team Code"},"court":{"type":"string","enum":["BC","FC"],"title":"Court"},"price":{"type":"number","title":"Price","description":"Frozen market price in millions."},"sale_value":{"type":"number","title":"Sale Value","description":"What selling him returns: his current price, or the price the owner entered for him."},"expected_points":{"type":"number","title":"Expected Points","description":"Expected Gameweek points on the Predixsport weekly rule, which is built from season performance, recent form and the calendar. Zero when the rule cannot value the player."},"games_left":{"type":"integer","minimum":0.0,"title":"Games Left"},"status":{"type":"string","enum":["rule_valued","no_form_record","no_games_left","not_transactable"],"title":"Status"},"reason":{"type":"string","title":"Reason"}},"additionalProperties":false,"type":"object","required":["player_name","team_code","court","price","sale_value","expected_points","games_left","status","reason"],"title":"NBAFantasyTransferMove"},"NBAFantasyTransferOption":{"properties":{"moves":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Moves"},"paid_transfers":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Paid Transfers"},"transfer_point_cost":{"type":"integer","minimum":0.0,"title":"Transfer Point Cost","description":"Official points this plan costs beyond the free transfers of the Gameweek. The allowance and the price of an extra move are on the official rules page, not in the market payload."},"expected_points":{"type":"number","title":"Expected Points","description":"What this squad is expected to score: the best legal five it fields each Gameday of the window, summed over the window. It is the number every other line on this plan is decided on."},"fielded_points":{"type":"number","title":"Fielded Points","description":"The same number, named for what it measures."},"proxy_points":{"type":"number","title":"Proxy Points","description":"The old linear ranking, which counts every game of all ten players. It is kept beside the fielded total for provenance and decides nothing."},"net_expected_points":{"type":"number","title":"Net Expected Points","description":"Fielded points after the paid-transfer cost."},"gain_vs_keep":{"type":"number","title":"Gain Vs Keep","description":"Net expected points against keeping the squad."},"squad_value":{"type":"number","title":"Squad Value"},"bank_after":{"type":"number","title":"Bank After"},"incoming":{"items":{"$ref":"#/components/schemas/NBAFantasyTransferMove"},"type":"array","title":"Incoming"},"outgoing":{"items":{"$ref":"#/components/schemas/NBAFantasyTransferMove"},"type":"array","title":"Outgoing"},"squad":{"items":{"$ref":"#/components/schemas/NBAFantasySquadRow"},"type":"array","title":"Squad"}},"additionalProperties":false,"type":"object","required":["moves","paid_transfers","transfer_point_cost","expected_points","fielded_points","proxy_points","net_expected_points","gain_vs_keep","squad_value","bank_after","incoming","outgoing","squad"],"title":"NBAFantasyTransferOption"},"NBAFantasyTransfersRequest":{"properties":{"season":{"anyOf":[{"type":"string","pattern":"^20\\d{2}$"},{"type":"null"}],"title":"Season"},"bank":{"type":"number","maximum":1000.0,"minimum":0.0,"title":"Bank","description":"Millions in the bank on the official game, copied from the game and never guessed.","default":0.0},"max_incoming":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Max Incoming","description":"Moves you are willing to make. Omitted compares them all."},"free_transfers":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Free Transfers","description":"Free transfers left in this Gameweek. The game gives two and they do not carry over; each extra move costs 100 points.","default":2},"locked":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Locked","description":"Players to keep."},"sold":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Sold","description":"Players already sold."},"sell_prices":{"additionalProperties":{"type":"number","maximum":100.0,"minimum":0.0},"type":"object","maxProperties":10,"title":"Sell Prices","description":"What a sale actually returns for a player, in millions, when the owner knows better than the current price. The game charges a fee on the profit half of a sale and we do not hold anybody's purchase price, so the default is the current price."}},"additionalProperties":false,"type":"object","title":"NBAFantasyTransfersRequest"},"NBAFantasyTransfersResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"mode":{"type":"string","enum":["transfers"],"const":"transfers","title":"Mode","default":"transfers"},"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"preset":{"type":"string","title":"Preset"},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion"},"value_basis":{"type":"string","enum":["fielded_five"],"const":"fielded_five","title":"Value Basis","description":"What every total on this plan measures: the five the game would field each Gameday, never a sum of the ten.","default":"fielded_five"},"search":{"type":"string","enum":["exact","certified","truncated"],"title":"Search","description":"'exact' means every legal squad your bank can reach was compared, one program per number of moves, maximising the fielded value itself, and it is what a transfer plan always answers. 'certified' means the search stopped inside its optimality tolerance and `search_gap` says how close to the best plan there is it was proved to be. 'truncated' means the search ran out of time and the plan is the best found so far, not the best there is.","default":"exact"},"search_gap":{"type":"number","minimum":0.0,"title":"Search Gap","description":"How close to the best plan there is this one was PROVED to be, as a fraction. Zero on an exact search, which is every transfer plan.","default":0.0},"search_gap_tolerance":{"type":"number","minimum":0.0,"title":"Search Gap Tolerance","description":"The optimality gap the search was allowed to stop at. Zero means it was not allowed to stop before it had the best there is.","default":0.0},"window_days":{"type":"integer","minimum":0.0,"title":"Window Days","description":"Gamedays of the window the plan was decided over.","default":0},"value_wording":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value Wording"},"court_source":{"type":"string","enum":["official_market"],"const":"official_market","title":"Court Source","default":"official_market"},"board_digest":{"type":"string","title":"Board Digest"},"roster_revision":{"type":"integer","title":"Roster Revision"},"market":{"$ref":"#/components/schemas/NBAFantasyMarketReference"},"window":{"$ref":"#/components/schemas/NBAFantasyGameweekWindow"},"free_transfers":{"type":"integer","minimum":0.0,"title":"Free Transfers"},"extra_transfer_point_cost":{"type":"integer","minimum":0.0,"title":"Extra Transfer Point Cost"},"transfer_rules_source":{"type":"string","title":"Transfer Rules Source"},"sale_value_basis":{"type":"string","enum":["current_price","owner_override"],"title":"Sale Value Basis"},"sale_overrides":{"items":{"type":"string"},"type":"array","title":"Sale Overrides"},"max_incoming":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Max Incoming"},"bank_before":{"type":"number","title":"Bank Before"},"squad_value":{"type":"number","title":"Squad Value"},"purchasing_power":{"type":"number","title":"Purchasing Power"},"keep":{"$ref":"#/components/schemas/NBAFantasyTransferOption"},"options":{"items":{"$ref":"#/components/schemas/NBAFantasyTransferOption"},"type":"array","title":"Options"},"alternatives":{"items":{"$ref":"#/components/schemas/NBAFantasyTransferOption"},"type":"array","title":"Alternatives"},"recommended_moves":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Recommended Moves"},"recommended":{"$ref":"#/components/schemas/NBAFantasyTransferOption"},"no_change":{"type":"boolean","title":"No Change"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","preset","board_digest","roster_revision","market","window","free_transfers","extra_transfer_point_cost","transfer_rules_source","sale_value_basis","max_incoming","bank_before","squad_value","purchasing_power","keep","recommended_moves","recommended","no_change"],"title":"NBAFantasyTransfersResponse"},"NBAFantasyWeek":{"properties":{"players":{"items":{"$ref":"#/components/schemas/NBAFantasyWeekPlayer"},"type":"array","title":"Players"},"drops":{"items":{"type":"string"},"type":"array","title":"Drops","description":"The thinnest weeks in this squad. A statement about the calendar, not a recommendation to trade."},"horizon_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Horizon Days"},"preset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preset"}},"additionalProperties":false,"type":"object","title":"NBAFantasyWeek"},"NBAFantasyWeekPlayer":{"properties":{"player_name":{"type":"string","title":"Player Name"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"games":{"type":"integer","title":"Games","default":0},"expected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected"},"per_game":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Per Game"},"in_week":{"type":"boolean","title":"In Week","default":true}},"additionalProperties":false,"type":"object","required":["player_name"],"title":"NBAFantasyWeekPlayer"},"NBAMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"integer","title":"Season"},"home_team":{"$ref":"#/components/schemas/TeamStats"},"away_team":{"$ref":"#/components/schemas/TeamStats"},"predicted_point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Point Diff","description":"Expected (home_points - away_points) from the dedicated spread regressor."},"predicted_total_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Total Points","description":"Expected sum of points scored by both teams (dedicated total regressor)."},"players":{"items":{"$ref":"#/components/schemas/NBAPlayerProjection"},"type":"array","title":"Players","description":"Per-player projections from the player-props models. Empty list when no player rows exist for this match."},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the winner prediction (positive values favour the home team). Absent for rows predicted before the explainability rollout."},"actual":{"anyOf":[{"$ref":"#/components/schemas/NBAActual"},{"type":"null"}],"description":"Realized box-score result, present once the match has been played and reconciled. Null for upcoming / unplayed matches — the predictive fields above are identical either way."}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"NBAMatchDetail"},"NBAPerformance":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"source":{"type":"string","enum":["production","test"],"title":"Source"},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed matches aggregated into this snapshot."},"date_range":{"$ref":"#/components/schemas/DateRange"},"winner_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Winner Accuracy","description":"Fraction of matches where the winner classifier picked the correct side."},"point_diff_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Point Diff Mae","description":"Mean absolute error of (predicted_point_diff − actual_point_diff)."},"total_points_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Points Mae","description":"Mean absolute error of (predicted_total_points − actual_total_points). Null when migration 007 columns not yet populated."}},"type":"object","required":["source","sample_size","date_range"],"title":"NBAPerformance"},"NBAPlayerCalendarProjection":{"properties":{"name":{"type":"string","title":"Name"},"team":{"type":"string","title":"Team"},"is_home":{"type":"boolean","title":"Is Home"},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position","description":"Rank slot inside the published starting five, 1 to 5, and 0 for a roster player outside it or for a projection made with no published lineup. It is NOT a listed basketball position: read ``listed_position`` and ``court`` for that."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points"},"expected_rebounds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Rebounds"},"expected_assists":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Assists"},"expected_three_pointers_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Three Pointers Made","description":"Expected three-pointers made."},"points_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Points Distribution"},"rebounds_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Rebounds Distribution"},"assists_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Assists Distribution"},"three_pointers_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Three Pointers Made Distribution"},"expected_steals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Steals"},"steals_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Steals Distribution"},"expected_blocks":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Blocks"},"blocks_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Blocks Distribution"},"expected_turnovers":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Turnovers"},"turnovers_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Turnovers Distribution"},"expected_field_goals_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Field Goals Made"},"field_goals_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Field Goals Made Distribution"},"expected_field_goals_attempted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Field Goals Attempted"},"field_goals_attempted_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Field Goals Attempted Distribution"},"expected_free_throws_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Free Throws Made"},"free_throws_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Free Throws Made Distribution"},"expected_free_throws_attempted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Free Throws Attempted"},"free_throws_attempted_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Free Throws Attempted Distribution"},"expected_nba_salary_cap_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Nba Salary Cap Points","description":"Expected NBA Salary Cap fantasy score (points + rebounds + 2 x assists + 3 x blocks + 3 x steals) from a model trained on that total directly, not from summing the component models. It is the only projected total with a distribution of its own."},"nba_salary_cap_points_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Nba Salary Cap Points Distribution"},"started":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Started","description":"True when the player is in the team's published starting five for this game, false when a lineup was published and he is not in it, null when no lineup had been published at projection time. Null is a real state here, not a missing value. This is the published lineup and nothing more: it is not a chance of playing and carries no injury information."},"projection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Projection Mode","description":"gameday (a lineup was published), gameday_no_lineup (the whole roster projected without one) or calendar (a lineup-free projection for a game later in the week). Null for rows written before the full-roster rollout."},"row_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Source","description":"Where the roster row came from: box_score, roster_union or published_lineup."},"low_confidence":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low Confidence","description":"True when this row rests on a thin recent record, typically a roster player with no recent appearance. The row is published flagged rather than dropped."},"listed_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Listed Position","description":"Listed basketball position, e.g. PG, SG, SF, PF, C, G-F."},"court":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Court","description":"BC for backcourt or FC for frontcourt, derived from the first token of listed_position. Fantasy formats that require a backcourt and frontcourt split use it."},"fantasy":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyScore"},{"type":"null"}],"description":"Projected score for the scoring preset named in the request's ``preset`` parameter. Absent when no preset was requested."},"match_id":{"type":"string","title":"Match Id","description":"Identifier of the scheduled game, the usual file_name."},"date":{"type":"string","format":"date","title":"Date","description":"Date the game is scheduled for."}},"type":"object","required":["name","team","is_home","match_id","date"],"title":"NBAPlayerCalendarProjection","description":"One lineup-free projection for a game later in the week.\n\nSame shape as the per-match projection plus the game it belongs to.\n``projection_mode`` is always ``calendar`` here, and ``started`` is\nalways null because no lineup exists days ahead by construction."},"NBAPlayerPerformanceResponse":{"properties":{"player":{"type":"string","title":"Player"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"season":{"type":"integer","title":"Season"},"season_label":{"type":"string","title":"Season Label"},"source":{"type":"string","title":"Source","description":"'backtest' = full-season holdout replay (season out-of-sample); 'production' = live predictions reconciled next-day"},"games_started":{"type":"integer","title":"Games Started"},"predictability_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predictability Score","description":"Mean CRPS skill of points + rebounds x 100. Higher = more predictable for the models."},"stats":{"items":{"$ref":"#/components/schemas/PlayerStatPerformance"},"type":"array","title":"Stats"},"recent_games":{"items":{"$ref":"#/components/schemas/PlayerGameLine"},"type":"array","title":"Recent Games","description":"Most recent games first; started games only"}},"type":"object","required":["player","season","season_label","source","games_started","stats","recent_games"],"title":"NBAPlayerPerformanceResponse"},"NBAPlayerProjection":{"properties":{"name":{"type":"string","title":"Name"},"team":{"type":"string","title":"Team"},"is_home":{"type":"boolean","title":"Is Home"},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position","description":"Rank slot inside the published starting five, 1 to 5, and 0 for a roster player outside it or for a projection made with no published lineup. It is NOT a listed basketball position: read ``listed_position`` and ``court`` for that."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points"},"expected_rebounds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Rebounds"},"expected_assists":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Assists"},"expected_three_pointers_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Three Pointers Made","description":"Expected three-pointers made."},"points_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Points Distribution"},"rebounds_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Rebounds Distribution"},"assists_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Assists Distribution"},"three_pointers_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Three Pointers Made Distribution"},"expected_steals":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Steals"},"steals_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Steals Distribution"},"expected_blocks":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Blocks"},"blocks_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Blocks Distribution"},"expected_turnovers":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Turnovers"},"turnovers_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Turnovers Distribution"},"expected_field_goals_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Field Goals Made"},"field_goals_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Field Goals Made Distribution"},"expected_field_goals_attempted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Field Goals Attempted"},"field_goals_attempted_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Field Goals Attempted Distribution"},"expected_free_throws_made":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Free Throws Made"},"free_throws_made_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Free Throws Made Distribution"},"expected_free_throws_attempted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Free Throws Attempted"},"free_throws_attempted_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Free Throws Attempted Distribution"},"expected_nba_salary_cap_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Nba Salary Cap Points","description":"Expected NBA Salary Cap fantasy score (points + rebounds + 2 x assists + 3 x blocks + 3 x steals) from a model trained on that total directly, not from summing the component models. It is the only projected total with a distribution of its own."},"nba_salary_cap_points_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Nba Salary Cap Points Distribution"},"started":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Started","description":"True when the player is in the team's published starting five for this game, false when a lineup was published and he is not in it, null when no lineup had been published at projection time. Null is a real state here, not a missing value. This is the published lineup and nothing more: it is not a chance of playing and carries no injury information."},"projection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Projection Mode","description":"gameday (a lineup was published), gameday_no_lineup (the whole roster projected without one) or calendar (a lineup-free projection for a game later in the week). Null for rows written before the full-roster rollout."},"row_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Source","description":"Where the roster row came from: box_score, roster_union or published_lineup."},"low_confidence":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low Confidence","description":"True when this row rests on a thin recent record, typically a roster player with no recent appearance. The row is published flagged rather than dropped."},"listed_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Listed Position","description":"Listed basketball position, e.g. PG, SG, SF, PF, C, G-F."},"court":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Court","description":"BC for backcourt or FC for frontcourt, derived from the first token of listed_position. Fantasy formats that require a backcourt and frontcourt split use it."},"fantasy":{"anyOf":[{"$ref":"#/components/schemas/NBAFantasyScore"},{"type":"null"}],"description":"Projected score for the scoring preset named in the request's ``preset`` parameter. Absent when no preset was requested."}},"type":"object","required":["name","team","is_home"],"title":"NBAPlayerProjection","description":"Per-player projections for an NBA match. Sourced from\n``nba_player_predictions``.\n\nSince September 2026 the row set is the projected ROSTER of each team,\naround 19 names a side, not the expected starting five, and twelve\nstats are projected instead of four. Every field added at that point\nis nullable and absent from rows written earlier, so a match predicted\nbefore the rollout still answers with the original four heads.\n\nHOW TO READ THE NUMBERS. All twelve models beat a naive rolling\naverage on the distribution. Five of them (points, rebounds, field\ngoals made, field goals attempted, NBA Salary Cap points) also beat it\non single-number error and can be used as point estimates. For the\nother seven (assists, three-pointers made, steals, blocks, turnovers,\nfree throws made, free throws attempted) use the distribution or a\nquantile band rather than the expectation alone.\n\nWHAT IS NOT HERE, BY DESIGN: no probability that a player features, no\ninjury or availability data of any kind. Availability is represented\nonly by ``started`` and ``projection_mode``, which describe the\npublished lineup of the day and nothing else."},"NBAPlayerWeekResponse":{"properties":{"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"The projection batch these rows come from, as YYYYMMDD. Null when no rows exist yet."},"count":{"type":"integer","title":"Count"},"players":{"items":{"$ref":"#/components/schemas/NBAPlayerCalendarProjection"},"type":"array","title":"Players"},"totals":{"items":{"$ref":"#/components/schemas/NBAPlayerWeekTotal"},"type":"array","title":"Totals","description":"One row per player for the whole window, best first: the ranked view a week planner reads. It is computed over every row that matches the filters, not only the ones returned in ``players``, and is capped by ``limit`` in its own right. Set ``include_rows=false`` to receive this block alone."},"dates":{"items":{"type":"string","format":"date"},"type":"array","title":"Dates","description":"Every day covered by the batch, ascending."},"resolved_names":{"items":{"type":"string"},"type":"array","title":"Resolved Names","description":"Stored names the ``names`` parameter matched, in the order they were written. Empty when the parameter was not used."},"unresolved_names":{"items":{"type":"string"},"type":"array","title":"Unresolved Names","description":"Entries of ``names`` that matched no player, or matched more than one and were therefore not guessed at. Echoed back so a pasted roster can be corrected rather than silently short."}},"type":"object","required":["count"],"title":"NBAPlayerWeekResponse","description":"Calendar-weighted NBA player projections for the days ahead.\n\nThese rows are the lineup-free mode: the same models run without a\npublished starting five, for games that have not been played. They are\ndeliberately NOT part of the per-match endpoint, which describes the\nday's slate only.\n\nAn empty list is a normal state, not an outage. NBA predictions are\ngated on the expected starting five, so the per-match surface exists\nonly on game day, and this look-ahead surface exists only once the\nrest-of-week projection mode has run for the current slate."},"NBAPlayerWeekTotal":{"properties":{"player_name":{"type":"string","title":"Player Name"},"team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team"},"court":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Court","description":"BC for backcourt or FC for frontcourt."},"listed_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Listed Position"},"games_this_week":{"type":"integer","title":"Games This Week","description":"Scheduled games for this player inside the batch."},"dates":{"items":{"type":"string","format":"date"},"type":"array","title":"Dates","description":"The days those games fall on."},"low_confidence":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low Confidence","description":"True when at least one of the player's rows rests on a thin recent record."},"expected_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Points","description":"Projected points over the whole week. Points is one of the five heads that cleared the single-number gate, so it is safe to read as a number; it is also what a category league can rank by, having no single score of its own."},"expectation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expectation","description":"The week's projected score under the requested preset, summed over the player's scheduled games. Null without a preset, for yahoo_9cat, which has no single score, and when a component the formula needs is missing on any one of his games."},"expectation_per_game":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expectation Per Game","description":"``expectation`` divided by ``games_this_week``: the same total read as a rate, which separates a busy calendar from a strong player."},"expectation_source":{"type":"string","title":"Expectation Source","description":"Which reading ``expectation`` holds: ``direct_model`` for nba_salary_cap, whose head is trained on the total itself, ``component_formula`` for the rest, ``none`` when no score could be formed.","default":"none"},"form_blend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Form Blend","description":"REALISED form, the only backward-looking number here: the mean of the player's season-to-date average and his last-five average of actual fantasy points under the requested preset, with games he did not play counted as zero. It is the ranking key of the captain rule Predixsport recommends, which has no model in it: back your best form player on the first day he is in his team's published five. Null when the player has no reconciled games yet, and for a category preset it is measured in NBA Salary Cap points, which is the only total a category league can borrow."}},"type":"object","required":["player_name","games_this_week"],"title":"NBAPlayerWeekTotal","description":"One player's whole week in a single row.\n\nThis is the ranked view a week planner reads: how many games the player\nhas scheduled inside the batch, and what the model expects him to score\nover all of them under the requested preset. The total is the sum of\nthe per-game expectations, which is exact for the mean by linearity. It\nis an EXPECTATION and nothing else: no band, no distribution and no\navailability, because a sum of dependent counts is not the convolution\nof the marginals and there is no published lineup days ahead."},"NBARatingHistoryPoint":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"Matchday the rating was computed for."},"team_name":{"type":"string","title":"Team Name"},"elo_rating":{"type":"number","title":"Elo Rating","description":"ELO after the matchday, on the fixed 1500-anchored scale (every team re-seeds at 1500 at the start of each season)."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played"},"position_overall":{"type":"integer","minimum":1.0,"title":"Position Overall","description":"League-wide rank by ELO on that matchday."}},"type":"object","required":["date","team_name","elo_rating","matches_played","position_overall"],"title":"NBARatingHistoryPoint"},"NBARatingsHistoryResponse":{"properties":{"season":{"type":"string","title":"Season"},"rating_basis":{"type":"string","enum":["absolute"],"const":"absolute","title":"Rating Basis","default":"absolute"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Points in this page."},"has_more":{"type":"boolean","title":"Has More","description":"True when further pages exist — advance `offset`."},"license":{"type":"string","title":"License","description":"Ratings-data license. Reuse permitted with attribution: link to https://www.predixsport.com","default":"CC BY 4.0"},"attribution":{"type":"string","title":"Attribution","default":"Reuse permitted with attribution: link to https://www.predixsport.com"},"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"points":{"items":{"$ref":"#/components/schemas/NBARatingHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["season","count","has_more","points"],"title":"NBARatingsHistoryResponse"},"NBARatingsResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"season":{"type":"integer","title":"Season"},"snapshot_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Snapshot Date"},"teams":{"items":{"$ref":"#/components/schemas/NBATeamRating"},"type":"array","title":"Teams"}},"type":"object","required":["season","snapshot_date","teams"],"title":"NBARatingsResponse"},"NBASearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"integer","title":"Season"},"home_team":{"type":"string","title":"Home Team"},"away_team":{"type":"string","title":"Away Team"}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"NBASearchMatch"},"NBASearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/NBASearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More","description":"True if more results are available beyond limit+offset."}},"type":"object","required":["matches","count","has_more"],"title":"NBASearchResponse"},"NBASeasonSimulation":{"properties":{"season":{"type":"string","title":"Season","description":"Season by ending year (`2026` = 2025-26)."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per day."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated seasons behind the probabilities."},"rs_complete":{"type":"boolean","title":"Rs Complete","description":"Regular season fully played.","default":false},"play_in_complete":{"type":"boolean","title":"Play In Complete","description":"Play-in tournament decided.","default":false},"is_completed":{"type":"boolean","title":"Is Completed","description":"True once the season has a champion.","default":false},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion","description":"Champion franchise, when the season is finished."},"teams":{"items":{"$ref":"#/components/schemas/NBATeamForecast"},"type":"array","title":"Teams","description":"Sorted by p_champion descending."},"bracket":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Bracket","description":"Predicted playoff bracket. Per conference and round, each series slot lists the most likely matchups with the probability the matchup occurs (`p`), each side's probability of winning the series (`win`), and the series score distribution (`scores`, e.g. `\"4-1\": 0.32` = wins the series 4-1 in 32% of the simulations where the matchup happens). Observed real series carry a `pinned` state."}},"type":"object","required":["season","run_date"],"title":"NBASeasonSimulation"},"NBASimulationsResponse":{"properties":{"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of season forecasts returned."},"seasons":{"items":{"$ref":"#/components/schemas/NBASeasonSimulation"},"type":"array","title":"Seasons"}},"type":"object","required":["count","seasons"],"title":"NBASimulationsResponse"},"NBATeamForecast":{"properties":{"team":{"type":"string","title":"Team","description":"Full franchise name (e.g. `Boston Celtics`)."},"conference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conference","description":"East | West."},"wins":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Wins","description":"Regular-season wins to date."},"losses":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Losses","description":"Regular-season losses to date."},"expected_rs_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Rs Wins","description":"Expected final regular-season wins (simulation mean)."},"p_champion":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Champion","description":"Probability of winning the NBA title — the share of simulated seasons this team won."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of reaching each stage, keyed by stage name (e.g. {\"playoffs\": 0.94, \"conf_finals\": 0.31, \"champion\": 0.12}; `play_in` = entering the play-in tournament). 1.0 = already there / guaranteed; 0.0 = ruled out."},"eliminated":{"type":"boolean","title":"Eliminated","description":"True only on pinned facts: the team lost an observed playoff series or sits outside a decided playoff field.","default":false}},"type":"object","required":["team","p_champion"],"title":"NBATeamForecast"},"NBATeamPerformanceResponse":{"properties":{"team":{"type":"string","title":"Team","description":"Stored franchise name — the join key across the API"},"slug":{"type":"string","title":"Slug"},"conference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conference","description":"'East' or 'West'"},"season":{"type":"string","title":"Season","description":"NBA season key: the closing year, e.g. '2026' = 2025-26"},"season_label":{"type":"string","title":"Season Label"},"source":{"type":"string","title":"Source","description":"'production': the rows are the game-day predictions as originally published during the season (NBA predictions exist only on game day, so the record is as-published by nature)."},"games":{"type":"integer","title":"Games","description":"Reconciled games behind the numbers"},"predictability":{"$ref":"#/components/schemas/TeamPredictability"},"markets":{"items":{"$ref":"#/components/schemas/TeamMarketAccuracy"},"type":"array","title":"Markets"},"upsets_caused":{"type":"integer","title":"Upsets Caused","description":"Games the team WON while our model gave it under 35% — wins as a clear underdog."},"upsets_suffered":{"type":"integer","title":"Upsets Suffered","description":"Games the team LOST while our model gave it 65% or more — losses as a clear favorite."},"recent_games":{"items":{"$ref":"#/components/schemas/TeamRecentGame"},"type":"array","title":"Recent Games","description":"Most recent reconciled games first"}},"type":"object","required":["team","slug","season","season_label","source","games","predictability","markets","upsets_caused","upsets_suffered","recent_games"],"title":"NBATeamPerformanceResponse"},"NBATeamRating":{"properties":{"name":{"type":"string","title":"Name"},"position_overall":{"type":"integer","minimum":1.0,"title":"Position Overall","description":"League-wide rank by ELO (1 = strongest)."},"position_conference":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Position Conference","description":"Rank within the team's conference (1 = strongest)."},"conference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conference","description":"Conference identifier (\"east\" or \"west\")."},"elo_rating":{"type":"number","title":"Elo Rating","description":"Predixsport ELO rating. Higher = stronger; the population is centred near 1500 by construction. See /blog/nba-elo-ratings for the full methodology."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played","description":"Games played this season."},"wins":{"type":"integer","minimum":0.0,"title":"Wins"},"losses":{"type":"integer","minimum":0.0,"title":"Losses"},"win_pct":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Win Pct","description":"Wins divided by matches_played, in [0, 1]."},"last_10_wins":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Last 10 Wins","description":"Wins in the team's most recent 10 regular-season games."},"last_10_losses":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Last 10 Losses"},"streak":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Streak","description":"Current run encoded as \"W4\" (won last 4) or \"L2\" (lost last 2)."}},"type":"object","required":["name","position_overall","elo_rating","matches_played","wins","losses","win_pct"],"title":"NBATeamRating"},"NBAUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["nba"],"const":"nba","title":"Sport","default":"nba"},"prediction_date":{"type":"string","format":"date","title":"Prediction Date"},"season":{"type":"integer","title":"Season"},"home_team":{"$ref":"#/components/schemas/TeamStats"},"away_team":{"$ref":"#/components/schemas/TeamStats"},"predicted_point_diff":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Point Diff","description":"Expected (home_points - away_points). Positive = home favoured. Source: dedicated Poisson spread regressor (independent of the winner classifier behind home_team.win_probability)."}},"type":"object","required":["match_id","prediction_date","season","home_team","away_team"],"title":"NBAUpcomingMatch","description":"One upcoming NBA match with the top-of-card predictions.\n\nPrediction horizon: NBA predictions are gated on the expected starting\nfive AND injury reports, which are published only a few HOURS before\ntip-off. Predictions therefore appear in the upcoming endpoint ONLY on\ngame day. An empty list for a ``days=14`` query — even during playoffs —\nis the normal steady state, NOT a pipeline failure: tomorrow's lineup\nis not yet known. Use ``days=1`` to retrieve today's NBA card.\n\nEach predicted metric comes from a SEPARATE, INDEPENDENTLY-TRAINED model\n(winner classifier, spread regressor, total-points regressor, plus\nper-player props on the match-detail endpoint). The models do not share\na joint likelihood — small numerical inconsistencies between them are\nby design. Audit reliability per model via ``GET /v1/calibration/nba/<model_id>``;\ndiscover ids via ``GET /v1/models?sport=nba``."},"NBAUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/NBAUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"NBAUpcomingResponse"},"PlayerGameLine":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Prediction file_name — join key across the API"},"date":{"type":"string","title":"Date"},"opponent":{"type":"string","title":"Opponent"},"is_home":{"type":"boolean","title":"Is Home"},"stats":{"additionalProperties":{"$ref":"#/components/schemas/PlayerGameStat"},"type":"object","title":"Stats","description":"Keys: points, rebounds, assists, threes"}},"type":"object","required":["match_id","date","opponent","is_home","stats"],"title":"PlayerGameLine"},"PlayerGameStat":{"properties":{"expected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected","description":"Model mean before tip-off"},"actual":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Actual","description":"Realized box-score value"},"p10":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P10","description":"10th percentile of the pre-game distribution"},"p90":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90","description":"90th percentile of the pre-game distribution"}},"type":"object","title":"PlayerGameStat"},"PlayerStatPerformance":{"properties":{"stat":{"type":"string","title":"Stat"},"games":{"type":"integer","title":"Games"},"avg_expected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Expected"},"avg_actual":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Actual"},"mae_model":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mae Model"},"mae_naive":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mae Naive","description":"Naive baseline = mean of season average and last-5 average at prediction time"},"crps_model":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crps Model"},"crps_naive":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Crps Naive"},"skill":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Skill","description":"1 - crps_model / crps_naive: distributional improvement over the naive baseline. Positive = the model tracks this player better than a rolling average."}},"type":"object","required":["stat","games"],"title":"PlayerStatPerformance"},"PlayerStats":{"properties":{"name":{"type":"string","title":"Name"},"win_probability":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Win Probability","description":"Probability of this player winning the match, in [0, 1]."}},"type":"object","required":["name"],"title":"PlayerStats","description":"Win-side projection for a tennis player."},"SeasonPerformance":{"properties":{"season":{"type":"string","title":"Season","description":"UEFA seasonYear — the season ENDING year. '2026' is the 2025-26 campaign."},"n_matches":{"type":"integer","minimum":0.0,"title":"N Matches"},"accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Accuracy"},"logloss":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Logloss"},"brier":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Brier"},"rps":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Rps"}},"type":"object","required":["season","n_matches"],"title":"SeasonPerformance","description":"One season's slice of a per-season performance record."},"SportEnum":{"type":"string","enum":["nba","euroleague","football","tennis","wta","champions"],"title":"SportEnum"},"SportInfo":{"properties":{"code":{"type":"string","enum":["nba","euroleague","football","tennis","wta","champions"],"title":"Code"},"name":{"type":"string","title":"Name"},"leagues":{"items":{"type":"string"},"type":"array","title":"Leagues","description":"Customer-facing league codes for this sport (empty for sports without league sub-divisions)."}},"type":"object","required":["code","name"],"title":"SportInfo"},"SportsResponse":{"properties":{"sports":{"items":{"$ref":"#/components/schemas/SportInfo"},"type":"array","title":"Sports"}},"type":"object","required":["sports"],"title":"SportsResponse"},"TeamMarketAccuracy":{"properties":{"market":{"type":"string","title":"Market","description":"'game_winner' (every predicted game) or 'confident_calls' (only games where the model gave one side at least 65%)"},"team_accuracy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Team Accuracy","description":"% of the team's games the as-published prediction called correctly"},"league_accuracy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"League Accuracy","description":"NBA-wide % over the same season"}},"type":"object","required":["market"],"title":"TeamMarketAccuracy"},"TeamPredictability":{"properties":{"brier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Brier","description":"Mean binary Brier score of our as-published game-day win probability over the team's reconciled games. Lower = the team's results were easier to call."},"league_brier":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"League Brier","description":"Same average over every reconciled game in the NBA"},"delta_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delta Pct","description":"How much easier (+) or harder (-) the team was to call than the NBA average, as a percentage of the league Brier."},"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier","description":"'on_script' (clearly easier than the league), 'in_line', or 'chaos' (clearly harder). Null below 15 reconciled games."}},"type":"object","title":"TeamPredictability"},"TeamRecentGame":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Prediction file_name — join key across the API"},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"opponent":{"type":"string","title":"Opponent"},"is_home":{"type":"boolean","title":"Is Home"},"team_win_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Team Win Probability","description":"As-published win probability for THIS team (%)"},"predicted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted","description":"Predicted outcome from the team's perspective: W / L"},"result":{"type":"string","title":"Result","description":"Realized outcome from the team's perspective"},"score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Score","description":"Final score from the team's perspective, e.g. '110-98'"},"prediction_correct":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Prediction Correct"}},"type":"object","required":["match_id","opponent","is_home","result"],"title":"TeamRecentGame"},"TeamStats":{"properties":{"name":{"type":"string","title":"Name"},"win_probability":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Win Probability","description":"Probability of this team winning, in [0, 1]. Null when the winner classifier has not been run for this match yet."}},"type":"object","required":["name"],"title":"TeamStats","description":"Win-side projection for an NBA team."},"TennisActual":{"properties":{"winner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner","description":"Name of the player who actually won the match."},"total_games_played":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Games Played","description":"Total games played across all sets. Compare with predicted_total_games."}},"type":"object","title":"TennisActual","description":"Realized result once the match has been played and reconciled\n(``{tennis|wta}_in_season_performance_frozen``). Absent until the match\nis played.\n\n``winner`` is the actual winner's NAME, aligned to the as-observed\nplayer ordering — never inferred from player position."},"TennisDistributions":{"properties":{"total_games":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Total Games"},"spread":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Spread"},"aces":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Aces"},"double_faults":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Double Faults"}},"type":"object","title":"TennisDistributions"},"TennisExactScoreProjection":{"properties":{"predicted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted","description":"Most likely set score in \"X-Y\" format relative to player_one."},"distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Distribution","description":"Probability per \"X-Y\" class. Masked to feasible outcomes for the tournament format."}},"type":"object","title":"TennisExactScoreProjection"},"TennisFantasyApplyRequest":{"properties":{"incoming_prices":{"additionalProperties":{"type":"integer","maximum":40.0,"minimum":1.0},"type":"object","maxProperties":8,"title":"Incoming Prices","description":"Official current prices for every recommended incoming player. Predixsport board prices are estimates and are never silently saved."}},"additionalProperties":false,"type":"object","title":"TennisFantasyApplyRequest"},"TennisFantasyBoard":{"properties":{"week_key":{"type":"string","title":"Week Key"},"open":{"type":"boolean","title":"Open"},"lock_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Lock Date","description":"First real tournament start date for this game week. The board fails closed when this date cannot be established."},"revision":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}],"title":"Revision","description":"Content revision of the canonical board used to detect advice that became stale after a same-day refresh."},"board_refs":{"items":{"$ref":"#/components/schemas/TennisFantasyBoardReference"},"type":"array","title":"Board Refs"},"players":{"items":{"$ref":"#/components/schemas/TennisFantasyBoardPlayer"},"type":"array","title":"Players"},"total_player_count":{"type":"integer","minimum":0.0,"title":"Total Player Count"},"omitted_player_count":{"type":"integer","minimum":0.0,"title":"Omitted Player Count","default":0}},"additionalProperties":false,"type":"object","required":["week_key","open","board_refs","players","total_player_count"],"title":"TennisFantasyBoard"},"TennisFantasyBoardPlayer":{"properties":{"name":{"type":"string","title":"Name"},"tournament":{"type":"string","title":"Tournament"},"credits_estimate":{"type":"integer","title":"Credits Estimate"},"credits_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credits As Of"},"expected_points":{"type":"number","title":"Expected Points"},"entered_this_week":{"type":"boolean","title":"Entered This Week","default":true},"title_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Title Probability"},"expected_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Wins"},"expected_matches":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Matches"},"aces_per_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aces Per Match"},"double_faults_per_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Double Faults Per Match"},"serve_profile_fallback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serve Profile Fallback"},"advancement_points":{"type":"number","title":"Advancement Points"},"aces_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aces Points"},"double_fault_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Double Fault Points"},"straight_sets_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Straight Sets Points"},"straight_model_available":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Straight Model Available"},"upset_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Upset Points"}},"additionalProperties":false,"type":"object","required":["name","tournament","credits_estimate","expected_points","advancement_points"],"title":"TennisFantasyBoardPlayer"},"TennisFantasyBoardReference":{"properties":{"tournament":{"type":"string","title":"Tournament"},"run_date":{"type":"string","title":"Run Date"},"generated_at":{"type":"string","title":"Generated At"}},"additionalProperties":false,"type":"object","required":["tournament","run_date","generated_at"],"title":"TennisFantasyBoardReference","description":"Customer-safe identity for one tournament projection board."},"TennisFantasyPriceChecks":{"properties":{"price_mismatches":{"items":{"$ref":"#/components/schemas/TennisFantasyPriceMismatch"},"type":"array","title":"Price Mismatches"},"incoming_prices_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incoming Prices As Of"},"credits_remaining_at_estimates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Remaining At Estimates"},"safety_margin_credits":{"type":"integer","title":"Safety Margin Credits"},"budget_tight":{"type":"boolean","title":"Budget Tight"},"safe_alternative":{"anyOf":[{"$ref":"#/components/schemas/TennisFantasySafeAlternative"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["safety_margin_credits","budget_tight"],"title":"TennisFantasyPriceChecks","description":"What to verify in the game before applying (added Oct 2026).\n\n``budget_tight`` = the plan leaves fewer than ``safety_margin_credits``\nunspent on estimated prices; ``safe_alternative`` is then the best plan\nthat keeps the margin (absent when it is the same transfer)."},"TennisFantasyPriceMismatch":{"properties":{"name":{"type":"string","title":"Name"},"entered_credits":{"type":"integer","title":"Entered Credits"},"estimated_credits":{"type":"integer","title":"Estimated Credits"},"estimated_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated As Of"}},"additionalProperties":false,"type":"object","required":["name","entered_credits","estimated_credits"],"title":"TennisFantasyPriceMismatch"},"TennisFantasyRecommendation":{"properties":{"snapshot_id":{"type":"integer","title":"Snapshot Id"},"season":{"type":"string","title":"Season"},"week_key":{"type":"string","title":"Week Key"},"optimizer_version":{"type":"string","title":"Optimizer Version"},"switches":{"type":"integer","title":"Switches"},"players_out":{"items":{"type":"string"},"type":"array","title":"Players Out"},"players_in":{"items":{"type":"string"},"type":"array","title":"Players In"},"active":{"items":{"type":"string"},"type":"array","title":"Active"},"alternates":{"items":{"type":"string"},"type":"array","title":"Alternates"},"captain":{"type":"string","title":"Captain"},"expected_points":{"type":"number","title":"Expected Points"},"net_expected_points":{"type":"number","title":"Net Expected Points"},"expected_gain":{"type":"number","title":"Expected Gain"},"decision_gain":{"type":"number","title":"Decision Gain"},"credits_remaining_estimate":{"type":"integer","title":"Credits Remaining Estimate"},"estimated_roster":{"items":{"$ref":"#/components/schemas/TennisFantasyRosterPlayer"},"type":"array","title":"Estimated Roster"},"lineup":{"items":{"$ref":"#/components/schemas/TennisFantasyRecommendationPlayer"},"type":"array","title":"Lineup"},"switch_options":{"items":{"$ref":"#/components/schemas/TennisFantasySwitchOption"},"type":"array","maxItems":9,"title":"Switch Options"},"incoming_prices_are_estimates":{"type":"boolean","title":"Incoming Prices Are Estimates","default":true},"price_checks":{"anyOf":[{"$ref":"#/components/schemas/TennisFantasyPriceChecks"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["snapshot_id","season","week_key","optimizer_version","switches","players_out","players_in","active","alternates","captain","expected_points","net_expected_points","expected_gain","decision_gain","credits_remaining_estimate","estimated_roster","created_at"],"title":"TennisFantasyRecommendation"},"TennisFantasyRecommendationPlayer":{"properties":{"name":{"type":"string","title":"Name"},"tournament":{"type":"string","title":"Tournament"},"credits_estimate":{"type":"integer","title":"Credits Estimate"},"credits_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credits As Of"},"expected_points":{"type":"number","title":"Expected Points"},"entered_this_week":{"type":"boolean","title":"Entered This Week","default":true},"title_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Title Probability"},"expected_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Wins"},"expected_matches":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Matches"},"aces_per_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aces Per Match"},"double_faults_per_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Double Faults Per Match"},"serve_profile_fallback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serve Profile Fallback"},"advancement_points":{"type":"number","title":"Advancement Points"},"aces_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aces Points"},"double_fault_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Double Fault Points"},"straight_sets_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Straight Sets Points"},"straight_model_available":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Straight Model Available"},"upset_points":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Upset Points"},"role":{"type":"string","enum":["captain","active","alternate"],"title":"Role"},"projected_lineup_points":{"type":"number","title":"Projected Lineup Points"}},"additionalProperties":false,"type":"object","required":["name","tournament","credits_estimate","expected_points","advancement_points","role","projected_lineup_points"],"title":"TennisFantasyRecommendationPlayer"},"TennisFantasyRosterPlayer":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"credits":{"type":"integer","maximum":40.0,"minimum":1.0,"title":"Credits"},"locked":{"type":"boolean","title":"Locked","default":false}},"additionalProperties":false,"type":"object","required":["name","credits"],"title":"TennisFantasyRosterPlayer"},"TennisFantasyRosterWrite":{"properties":{"season":{"type":"string","pattern":"^20\\d{2}$","title":"Season"},"roster":{"items":{"$ref":"#/components/schemas/TennisFantasyRosterPlayer"},"type":"array","maxItems":8,"minItems":8,"title":"Roster"},"budget_remaining":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Budget Remaining","default":0},"free_switches":{"type":"integer","maximum":12.0,"minimum":0.0,"title":"Free Switches","default":4},"max_extra_switches":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Max Extra Switches","default":0},"chip_mode":{"type":"string","enum":["none","triple_bounce","alternates","swing_switch"],"title":"Chip Mode","default":"none"},"bank_switches":{"type":"boolean","title":"Bank Switches","default":true}},"additionalProperties":false,"type":"object","required":["season","roster"],"title":"TennisFantasyRosterWrite"},"TennisFantasySafeAlternative":{"properties":{"switches":{"type":"integer","title":"Switches"},"players_in":{"items":{"type":"string"},"type":"array","title":"Players In"},"players_out":{"items":{"type":"string"},"type":"array","title":"Players Out"},"active":{"items":{"type":"string"},"type":"array","title":"Active"},"alternates":{"items":{"type":"string"},"type":"array","title":"Alternates"},"captain":{"type":"string","title":"Captain"},"net_expected_points":{"type":"number","title":"Net Expected Points"},"points_cost":{"type":"number","title":"Points Cost"},"credits_remaining":{"type":"integer","title":"Credits Remaining"}},"additionalProperties":false,"type":"object","required":["switches","players_in","players_out","active","alternates","captain","net_expected_points","points_cost","credits_remaining"],"title":"TennisFantasySafeAlternative"},"TennisFantasySavedRoster":{"properties":{"season":{"type":"string","pattern":"^20\\d{2}$","title":"Season"},"roster":{"items":{"$ref":"#/components/schemas/TennisFantasyRosterPlayer"},"type":"array","maxItems":8,"minItems":8,"title":"Roster"},"budget_remaining":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Budget Remaining","default":0},"free_switches":{"type":"integer","maximum":12.0,"minimum":0.0,"title":"Free Switches","default":4},"max_extra_switches":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Max Extra Switches","default":0},"chip_mode":{"type":"string","enum":["none","triple_bounce","alternates","swing_switch"],"title":"Chip Mode","default":"none"},"bank_switches":{"type":"boolean","title":"Bank Switches","default":true},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["season","roster","updated_at"],"title":"TennisFantasySavedRoster"},"TennisFantasyStateResponse":{"properties":{"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"roster":{"$ref":"#/components/schemas/TennisFantasySavedRoster"},"board":{"anyOf":[{"$ref":"#/components/schemas/TennisFantasyBoard"},{"type":"null"}]},"latest_recommendation":{"anyOf":[{"$ref":"#/components/schemas/TennisFantasyRecommendation"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["roster"],"title":"TennisFantasyStateResponse"},"TennisFantasySwitchOption":{"properties":{"switches":{"type":"integer","title":"Switches"},"players_in":{"items":{"type":"string"},"type":"array","title":"Players In"},"players_out":{"items":{"type":"string"},"type":"array","title":"Players Out"},"active":{"items":{"type":"string"},"type":"array","title":"Active"},"alternates":{"items":{"type":"string"},"type":"array","title":"Alternates"},"captain":{"type":"string","title":"Captain"},"net_expected_points":{"type":"number","title":"Net Expected Points"},"points_gain":{"type":"number","title":"Points Gain"},"penalty":{"type":"number","title":"Penalty"},"credits_remaining":{"type":"integer","title":"Credits Remaining"},"free_switches_left":{"type":"integer","title":"Free Switches Left"},"recommended":{"type":"boolean","title":"Recommended"},"switch_reserve_cost":{"type":"number","title":"Switch Reserve Cost"}},"additionalProperties":false,"type":"object","required":["switches","players_in","players_out","active","alternates","captain","net_expected_points","points_gain","penalty","credits_remaining","free_switches_left","recommended","switch_reserve_cost"],"title":"TennisFantasySwitchOption"},"TennisMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"date":{"type":"string","format":"date","title":"Date","description":"Tournament-scheduled START day, NOT the actual match day. Shared by every match of the same tournament across all rounds. Use ``tournament.round`` to gauge how far into the tournament this match falls."},"season":{"type":"string","title":"Season"},"tournament":{"$ref":"#/components/schemas/TournamentInfo"},"player_one":{"$ref":"#/components/schemas/TennisPlayerDetail","description":"First player in as-observed order from data ingestion. NOT winner-first canonicalised. All player-relative predictions (spread, exact_score, this player's aces and double faults) are aligned to this ordering."},"player_two":{"$ref":"#/components/schemas/TennisPlayerDetail"},"predicted_total_games":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Total Games","description":"Rounded mean of the Poisson total-games regressor. Equivalent to `round(expected_total_games)` and aligned with the value shown on predixsport.com. Use `expected_total_games` for the un-rounded mean and `distributions.total_games` for the full PMF."},"expected_total_games":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Total Games","description":"Mean (expected value) of the Poisson total-games regressor."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result"},"tiebreak":{"anyOf":[{"$ref":"#/components/schemas/TennisTiebreakProjection"},{"type":"null"}]},"exact_score":{"anyOf":[{"$ref":"#/components/schemas/TennisExactScoreProjection"},{"type":"null"}]},"expected_aces":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Aces","description":"Expected total aces in the match: the sum of the two per-player projections carried by `player_one` and `player_two`, with `distributions.aces` the convolution of their distributions. The per-player fields are null for matches predicted before the per-player rollout on that tour, and the total keeps the meaning it has always had either way."},"expected_double_faults":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Double Faults","description":"Expected total double faults in the match. Same derivation as `expected_aces`, on both tours."},"expected_spread":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Spread","description":"Expected (player_one_games - player_two_games). Sign convention follows the as-observed player order — negative means player_two is favoured."},"distributions":{"$ref":"#/components/schemas/TennisDistributions"},"cdfs":{"$ref":"#/components/schemas/TennisDistributions"},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the winner prediction (positive values favour player_one, in as-observed order). Absent for rows predicted before the explainability rollout."},"actual":{"anyOf":[{"$ref":"#/components/schemas/TennisActual"},{"type":"null"}],"description":"Realized result, present once the match has been played and reconciled. Null for upcoming / unplayed matches — the predictive fields above are identical either way."}},"type":"object","required":["match_id","date","season","tournament","player_one","player_two"],"title":"TennisMatchDetail"},"TennisMatchLine":{"properties":{"match_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Id","description":"Prediction file_name — join key across the API"},"date":{"type":"string","title":"Date"},"opponent":{"type":"string","title":"Opponent"},"won":{"type":"boolean","title":"Won"},"favorite_prob":{"type":"number","title":"Favorite Prob","description":"Pre-match probability of the model's favorite (0-1)"},"upset":{"type":"boolean","title":"Upset","description":"True when the model's favorite lost"}},"type":"object","required":["date","opponent","won","favorite_prob","upset"],"title":"TennisMatchLine"},"TennisPerformance":{"properties":{"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"source":{"type":"string","enum":["production","test"],"title":"Source"},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed matches aggregated into this snapshot."},"date_range":{"$ref":"#/components/schemas/DateRange"},"winner_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Winner Accuracy","description":"Fraction of matches where the winner classifier picked the correct player."},"total_games_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Games Mae","description":"Mean absolute error of the Poisson total-games regressor vs the observed game count."}},"type":"object","required":["source","sample_size","date_range"],"title":"TennisPerformance"},"TennisPlayerDetail":{"properties":{"name":{"type":"string","title":"Name"},"win_probability":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Win Probability","description":"Probability of this player winning the match, in [0, 1]."},"expected_aces":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Aces","description":"Expected aces served by THIS player. Sums with the other player's value to `expected_aces`."},"aces_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Aces Distribution","description":"Probability per ace count for this player, as {count: probability}."},"aces_cdf":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Aces Cdf","description":"Cumulative probability P(aces <= count) for this player."},"expected_double_faults":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Double Faults","description":"Expected double faults hit by THIS player. Sums with the other player's value to `expected_double_faults`."},"double_faults_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Double Faults Distribution","description":"Probability per double-fault count for this player, as {count: probability}."},"double_faults_cdf":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Double Faults Cdf","description":"Cumulative probability P(double faults <= count) for this player."},"actual_aces":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Actual Aces","description":"Aces THIS player actually served, once the match is played and reconciled. Compare with `expected_aces` and score the call against `aces_distribution`. Null while the match is ahead, and for matches played before the per-player rollout."},"actual_double_faults":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Actual Double Faults","description":"Double faults THIS player actually hit, once the match is played and reconciled. Same availability rule as `actual_aces`."}},"type":"object","required":["name"],"title":"TennisPlayerDetail","description":"The ``player_one`` / ``player_two`` object of a tennis-family match\ndetail: the win probability plus that player's OWN serve projections.\n\nThe serve fields hang off the player they describe rather than sitting\nflat on the payload as ``expected_aces_player_one``. Same rule as the\nwin probability (see ``src.models.predictions``): every player-relative\nnumber lives inside the object whose ``name`` it belongs to, so it\ncannot be mis-attributed.\n\nPopulated for ATP ``tennis`` since migration 060 and WTA since\nmigration 063. Null for matches predicted before the relevant rollout.\n\n``actual_aces`` / ``actual_double_faults`` are the realized counts of a\nplayed match (migrations 061 / 064), matched to this object by player\nNAME rather than by slot: the reconciliation record carries the\nas-observed order of the LATEST data ingestion while the prediction\ncarries the as-observed order of the day it was made, and pairing those\nby position would hand one player's serve numbers to the other whenever\nthe two disagree."},"TennisPlayerForecast":{"properties":{"player":{"type":"string","title":"Player","description":"Player display name."},"seed":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Seed","description":"Seeding, when seeded."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Country code as observed in the draw."},"qualifier_slot":{"type":"boolean","title":"Qualifier Slot","description":"True when this entry is an undetermined qualifier slot priced with field-average ratings rather than a known player.","default":false},"p_title":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Title","description":"Probability of winning the tournament — the share of simulated tournaments this player won."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of reaching each round, keyed by round name (e.g. {\"Quarterfinals\": 0.61, \"Final\": 0.18, \"title\": 0.07}). 1.0 = already there / guaranteed; 0.0 = eliminated."},"expected_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Wins","description":"Expected number of matches won from here (simulation mean)."},"rounds_won":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Rounds Won","description":"Matches already won in this draw (byes included)."},"eliminated":{"type":"boolean","title":"Eliminated","description":"True once the player is out of the tournament.","default":false},"atp_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Atp Id","description":"ATP player code (e.g. `S0AG`) — stable join key across endpoints."}},"type":"object","required":["player","p_title"],"title":"TennisPlayerForecast"},"TennisPlayerPerformanceResponse":{"properties":{"player":{"type":"string","title":"Player"},"tour":{"type":"string","title":"Tour"},"seasons":{"items":{"$ref":"#/components/schemas/TennisSeasonScript"},"type":"array","title":"Seasons","description":"Newest season first; seasons with 15+ reconciled matches"},"recent_matches":{"items":{"$ref":"#/components/schemas/TennisMatchLine"},"type":"array","title":"Recent Matches","description":"Most recent matches of the newest listed season, newest first"}},"type":"object","required":["player","tour","seasons","recent_matches"],"title":"TennisPlayerPerformanceResponse"},"TennisPlayerRating":{"properties":{"name":{"type":"string","title":"Name"},"elo_rating":{"type":"number","title":"Elo Rating","description":"Overall surface-agnostic Predixsport ELO rating. See /blog/tennis-elo-ratings for the methodology."},"elo_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Elo Rank","description":"Tour rank by `elo_rating` (1 = strongest)."},"glicko2_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko2 Rating","description":"Glicko-2 rating — adds Rating Deviation (uncertainty) on top of ELO. Pairs with `glicko2_rd` to gauge confidence."},"glicko2_rd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Glicko2 Rd","description":"Rating Deviation — Glicko-2's uncertainty estimate. Lower = more confident; new / returning players see higher RD."},"glicko2_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Glicko2 Rank"},"surface_hard_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Surface Hard Rating","description":"Hard-court-specific form index. Surface ratings live in a different scale than overall ELO — interpret them relative to the tour, not against `elo_rating`."},"surface_hard_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Surface Hard Rank"},"surface_clay_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Surface Clay Rating","description":"Clay-court form index. See `surface_hard_rating` note."},"surface_clay_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Surface Clay Rank"},"surface_grass_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Surface Grass Rating","description":"Grass-court form index. See `surface_hard_rating` note."},"surface_grass_rank":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Surface Grass Rank"},"form_index":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Form Index","description":"Short-window recency score (~last 8 matches) blending result, opponent strength and surface. Scaled so most active players sit in [0, 100]."},"race_rankings":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Race Rankings","description":"ATP Race points — calendar-year ranking points. Roughly proportional to current-season title pursuit."},"matches_played":{"type":"integer","minimum":0.0,"title":"Matches Played","default":0},"matches_won":{"type":"integer","minimum":0.0,"title":"Matches Won","default":0},"win_rate":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Win Rate","description":"matches_won / matches_played, in [0, 1].","default":0.0}},"type":"object","required":["name","elo_rating"],"title":"TennisPlayerRating"},"TennisRatingHistoryPoint":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"Match date the rating was computed for."},"player_name":{"type":"string","title":"Player Name"},"opponent_name":{"type":"string","title":"Opponent Name","description":"The other player in the match (as-observed name)."},"tournament":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tournament","description":"Tournament the match belonged to (null if unrecorded)."},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"Court surface — hard / clay / grass (null if unrecorded)."},"won":{"type":"boolean","title":"Won","description":"True if `player_name` won this match."},"elo_pre":{"type":"number","title":"Elo Pre","description":"Season ELO entering the match (points-seeded each January)."},"elo_rating":{"type":"number","title":"Elo Rating","description":"Season ELO after the match. Season-scoped: it re-seeds from tour entry points every January, so never compare it across seasons."}},"type":"object","required":["date","player_name","opponent_name","won","elo_pre","elo_rating"],"title":"TennisRatingHistoryPoint"},"TennisRatingsHistoryResponse":{"properties":{"season":{"type":"string","title":"Season"},"rating_basis":{"type":"string","enum":["points_seeded"],"const":"points_seeded","title":"Rating Basis","default":"points_seeded"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Points in this page."},"has_more":{"type":"boolean","title":"Has More","description":"True when further pages exist — advance `offset`."},"license":{"type":"string","title":"License","description":"Ratings-data license. Reuse permitted with attribution: link to https://www.predixsport.com","default":"CC BY 4.0"},"attribution":{"type":"string","title":"Attribution","default":"Reuse permitted with attribution: link to https://www.predixsport.com"},"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"points":{"items":{"$ref":"#/components/schemas/TennisRatingHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["season","count","has_more","points"],"title":"TennisRatingsHistoryResponse"},"TennisRatingsResponse":{"properties":{"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"season":{"type":"string","title":"Season"},"snapshot_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Date"},"players":{"items":{"$ref":"#/components/schemas/TennisPlayerRating"},"type":"array","title":"Players"}},"type":"object","required":["season","players"],"title":"TennisRatingsResponse"},"TennisSearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"date":{"type":"string","format":"date","title":"Date"},"season":{"type":"string","title":"Season"},"tournament_name":{"type":"string","title":"Tournament Name"},"surface":{"type":"string","title":"Surface"},"round":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Round"},"player_one_name":{"type":"string","title":"Player One Name","description":"Player one in as-observed order — NOT winner-first."},"player_two_name":{"type":"string","title":"Player Two Name"}},"type":"object","required":["match_id","date","season","tournament_name","surface","player_one_name","player_two_name"],"title":"TennisSearchMatch"},"TennisSearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/TennisSearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["matches","count","has_more"],"title":"TennisSearchResponse"},"TennisSeasonScript":{"properties":{"season":{"type":"string","title":"Season"},"source":{"type":"string","title":"Source","description":"'backtest' = strict out-of-sample holdout of the current model; 'production' = the as-published daily record. One source per season, never mixed."},"matches":{"type":"integer","title":"Matches"},"wins":{"type":"integer","title":"Wins"},"losses":{"type":"integer","title":"Losses"},"upsets":{"type":"integer","title":"Upsets","description":"Matches where the model's favorite lost"},"expected_upsets":{"type":"number","title":"Expected Upsets","description":"Sum of the priced favorite-loses probabilities across this player's matches — the upset count a calibrated model expects for that exact schedule"},"extra_upsets":{"type":"number","title":"Extra Upsets","description":"upsets - expected_upsets (negative = steadier than priced)"},"ci":{"type":"number","title":"Ci","description":"95% interval half-width on extra_upsets"},"z":{"type":"number","title":"Z"},"tier":{"type":"string","title":"Tier","description":"'on_script' (Straight sets, z <= -1.5), 'in_line' (On serve) or 'chaos' (Wild card, z >= 1.5). A SEASON-level verdict: it does not persist year over year and is never a career label."},"model_hit_pct":{"type":"number","title":"Model Hit Pct","description":"Share of this player's matches where the model's favorite won"},"decisiveness_pct":{"type":"number","title":"Decisiveness Pct","description":"Average favorite probability in this player's matches"}},"type":"object","required":["season","source","matches","wins","losses","upsets","expected_upsets","extra_upsets","ci","z","tier","model_hit_pct","decisiveness_pct"],"title":"TennisSeasonScript"},"TennisSimulationsResponse":{"properties":{"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of tournaments returned."},"tournaments":{"items":{"$ref":"#/components/schemas/TennisTournamentSimulation"},"type":"array","title":"Tournaments"}},"type":"object","required":["count","tournaments"],"title":"TennisSimulationsResponse"},"TennisTiebreakProjection":{"properties":{"probability_yes":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability Yes"},"probability_no":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Probability No"},"predicted":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted"}},"type":"object","title":"TennisTiebreakProjection"},"TennisTournamentSimulation":{"properties":{"tournament":{"type":"string","title":"Tournament","description":"Tournament slug (e.g. `stuttgart`)."},"season":{"type":"string","title":"Season","description":"Season, 4-digit year for tennis."},"tournament_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tournament Type","description":"ATP: grand_slam | masters_1000 | atp_500 | atp_250. WTA: grand_slam | wta_1000 | wta_500 | wta_250."},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"hard | clay | grass."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per day."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated tournaments behind the probabilities."},"n_rounds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Rounds","description":"Bracket depth (rounds)."},"n_completed_rounds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Completed Rounds","description":"Fully decided rounds at forecast time."},"is_completed":{"type":"boolean","title":"Is Completed","description":"True once the tournament has a champion.","default":false},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion","description":"Champion name, when the tournament is finished."},"players":{"items":{"$ref":"#/components/schemas/TennisPlayerForecast"},"type":"array","title":"Players","description":"Sorted by p_title descending."}},"type":"object","required":["tournament","season","run_date"],"title":"TennisTournamentSimulation"},"TennisUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["tennis"],"const":"tennis","title":"Sport","default":"tennis"},"date":{"type":"string","format":"date","title":"Date","description":"Tournament-scheduled START day, NOT the actual day of play. Shared by every match of the same tournament across all rounds. Do not interpret a value in the past as 'match already played' — tournaments typically run 7–14 days from this date."},"season":{"type":"string","title":"Season"},"tournament":{"$ref":"#/components/schemas/TournamentInfo"},"player_one":{"$ref":"#/components/schemas/PlayerStats","description":"First player in as-observed order. NOT winner-first canonicalised. All player-relative predictions are aligned to this ordering."},"player_two":{"$ref":"#/components/schemas/PlayerStats"},"predicted_total_games":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Total Games","description":"Rounded mean of the Poisson total-games regressor. Use the match-detail endpoint for the un-rounded mean and the full distribution. Independent of the winner classifier."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"Player name predicted to win (winner classifier output)."}},"type":"object","required":["match_id","date","season","tournament","player_one","player_two"],"title":"TennisUpcomingMatch","description":"One upcoming tennis match with the top-of-card predictions.\n\nDate semantics — IMPORTANT: ``date`` is the TOURNAMENT-SCHEDULED START\nDAY, NOT the day this specific match is played. Tennis Masters / ATP\nevents span 7–14 days; the same ``date`` is shared by every match of\nthe same tournament across all rounds. Use ``tournament.round`` (where\navailable in match-detail) to infer how far into the tournament a\nmatch falls. The ``?days`` query parameter on this endpoint is kept\nfor API symmetry but is NOT applied to tennis (ATP frequently\nreschedules matches and ``date`` isn't always updated downstream;\nfiltering on it would drop legitimate upcoming matches). The endpoint\ninstead returns the latest inference snapshot in full, which by\nconstruction covers the active tournament's near future.\n\nTennis predictions come from SEVEN SEPARATE, INDEPENDENTLY-TRAINED\nmodels: winner classifier, total_games Poisson regressor, spread\nPoisson regressor, aces / double_faults regressors, tiebreak\nclassifier, exact_score multiclass softmax. The models do not share a\njoint likelihood: for example the winner-classifier marginal need not\nequal the sum of exact-score outcomes that favour the same player,\nand a 60% win probability need not pair with any specific total_games\ndistribution. Inconsistencies between these metrics are EXPECTED BY\nDESIGN. Audit each model via ``GET /v1/calibration/tennis/<model_id>``;\ndiscover ids via ``GET /v1/models?sport=tennis``."},"TennisUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/TennisUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"TennisUpcomingResponse"},"TournamentInfo":{"properties":{"name":{"type":"string","title":"Name"},"location":{"type":"string","title":"Location"},"surface":{"type":"string","title":"Surface"},"type":{"type":"string","title":"Type","description":"Tournament category (e.g. grand_slam, masters_1000, atp_250)."},"round":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Round","description":"Numeric round identifier as recorded by the data pipeline (higher = later round)."}},"type":"object","required":["name","location","surface","type"],"title":"TournamentInfo"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WTAMatchDetail":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"date":{"type":"string","format":"date","title":"Date","description":"Tournament-scheduled START day, NOT the actual match day. Shared by every match of the same tournament across all rounds. Use ``tournament.round`` to gauge how far into the tournament this match falls."},"season":{"type":"string","title":"Season"},"tournament":{"$ref":"#/components/schemas/TournamentInfo"},"player_one":{"$ref":"#/components/schemas/TennisPlayerDetail","description":"First player in as-observed order from data ingestion. NOT winner-first canonicalised. All player-relative predictions (spread, exact_score, this player's aces and double faults) are aligned to this ordering."},"player_two":{"$ref":"#/components/schemas/TennisPlayerDetail"},"predicted_total_games":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Total Games","description":"Rounded mean of the Poisson total-games regressor. Equivalent to `round(expected_total_games)` and aligned with the value shown on predixsport.com. Use `expected_total_games` for the un-rounded mean and `distributions.total_games` for the full PMF."},"expected_total_games":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Total Games","description":"Mean (expected value) of the Poisson total-games regressor."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result"},"tiebreak":{"anyOf":[{"$ref":"#/components/schemas/TennisTiebreakProjection"},{"type":"null"}]},"exact_score":{"anyOf":[{"$ref":"#/components/schemas/TennisExactScoreProjection"},{"type":"null"}]},"expected_aces":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Aces","description":"Expected total aces in the match: the sum of the two per-player projections carried by `player_one` and `player_two`, with `distributions.aces` the convolution of their distributions. The per-player fields are null for matches predicted before the per-player rollout on that tour, and the total keeps the meaning it has always had either way."},"expected_double_faults":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Double Faults","description":"Expected total double faults in the match. Same derivation as `expected_aces`, on both tours."},"expected_spread":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Spread","description":"Expected (player_one_games - player_two_games). Sign convention follows the as-observed player order — negative means player_two is favoured."},"distributions":{"$ref":"#/components/schemas/TennisDistributions"},"cdfs":{"$ref":"#/components/schemas/TennisDistributions"},"key_factors":{"anyOf":[{"$ref":"#/components/schemas/KeyFactors"},{"type":"null"}],"description":"Concept-level attribution of the winner prediction (positive values favour player_one, in as-observed order). Absent for rows predicted before the explainability rollout."},"actual":{"anyOf":[{"$ref":"#/components/schemas/TennisActual"},{"type":"null"}],"description":"Realized result, present once the match has been played and reconciled. Null for upcoming / unplayed matches — the predictive fields above are identical either way."}},"type":"object","required":["match_id","date","season","tournament","player_one","player_two"],"title":"WTAMatchDetail","description":"WTA (women's tennis) match detail — same contract as ATP tennis.\n\nEvery WTA match is best-of-three sets (Grand Slams included), so the\nexact-score distribution only carries the four bo3 classes.\n``key_factors`` is populated, as it is on the ATP tour.\n\nServe projections are per player here too (one model per stat, two\noutputs): ``player_one`` and ``player_two`` each carry their own aces and\ndouble faults, and the match totals are derived by summing the two\nprojections. Those per-player fields are null only for matches predicted\nbefore the August 2026 rollout."},"WTAPerformance":{"properties":{"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"source":{"type":"string","enum":["production","test"],"title":"Source"},"sample_size":{"type":"integer","minimum":0.0,"title":"Sample Size","description":"Number of completed matches aggregated into this snapshot."},"date_range":{"$ref":"#/components/schemas/DateRange"},"winner_accuracy":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Winner Accuracy","description":"Fraction of matches where the winner classifier picked the correct player."},"total_games_mae":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Games Mae","description":"Mean absolute error of the Poisson total-games regressor vs the observed game count."}},"type":"object","required":["source","sample_size","date_range"],"title":"WTAPerformance","description":"WTA (women's tennis) — same headline metrics as ATP tennis."},"WTAPlayerForecast":{"properties":{"player":{"type":"string","title":"Player","description":"Player display name."},"seed":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Seed","description":"Seeding, when seeded."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Country code as observed in the draw."},"qualifier_slot":{"type":"boolean","title":"Qualifier Slot","description":"True when this entry is an undetermined qualifier slot priced with field-average ratings rather than a known player.","default":false},"p_title":{"type":"number","maximum":1.0,"minimum":0.0,"title":"P Title","description":"Probability of winning the tournament — the share of simulated tournaments this player won."},"p_reach":{"additionalProperties":{"type":"number"},"type":"object","title":"P Reach","description":"Probability of reaching each round, keyed by round name (e.g. {\"Quarterfinals\": 0.61, \"Final\": 0.18, \"title\": 0.07}). 1.0 = already there / guaranteed; 0.0 = eliminated."},"expected_wins":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Wins","description":"Expected number of matches won from here (simulation mean)."},"rounds_won":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Rounds Won","description":"Matches already won in this draw (byes included)."},"eliminated":{"type":"boolean","title":"Eliminated","description":"True once the player is out of the tournament.","default":false},"wta_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wta Id","description":"WTA player id — stable join key across endpoints."}},"type":"object","required":["player","p_title"],"title":"WTAPlayerForecast"},"WTARatingsHistoryResponse":{"properties":{"season":{"type":"string","title":"Season"},"rating_basis":{"type":"string","enum":["points_seeded"],"const":"points_seeded","title":"Rating Basis","default":"points_seeded"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Points in this page."},"has_more":{"type":"boolean","title":"Has More","description":"True when further pages exist — advance `offset`."},"license":{"type":"string","title":"License","description":"Ratings-data license. Reuse permitted with attribution: link to https://www.predixsport.com","default":"CC BY 4.0"},"attribution":{"type":"string","title":"Attribution","default":"Reuse permitted with attribution: link to https://www.predixsport.com"},"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"points":{"items":{"$ref":"#/components/schemas/TennisRatingHistoryPoint"},"type":"array","title":"Points"}},"type":"object","required":["season","count","has_more","points"],"title":"WTARatingsHistoryResponse","description":"WTA (women's tennis) — same per-match ELO-log shape as ATP tennis,\nread from ``wta_elo_progression``."},"WTARatingsResponse":{"properties":{"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"season":{"type":"string","title":"Season"},"snapshot_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Date"},"players":{"items":{"$ref":"#/components/schemas/TennisPlayerRating"},"type":"array","title":"Players"}},"type":"object","required":["season","players"],"title":"WTARatingsResponse","description":"WTA (women's tennis) ratings — same per-player shape as ATP tennis\n(``race_rankings`` carries WTA Race points for this sport)."},"WTASearchMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"date":{"type":"string","format":"date","title":"Date"},"season":{"type":"string","title":"Season"},"tournament_name":{"type":"string","title":"Tournament Name"},"surface":{"type":"string","title":"Surface"},"round":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Round"},"player_one_name":{"type":"string","title":"Player One Name","description":"Player one in as-observed order — NOT winner-first."},"player_two_name":{"type":"string","title":"Player Two Name"}},"type":"object","required":["match_id","date","season","tournament_name","surface","player_one_name","player_two_name"],"title":"WTASearchMatch","description":"WTA (women's tennis) search row — same contract as ATP tennis."},"WTASearchResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/WTASearchMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["matches","count","has_more"],"title":"WTASearchResponse"},"WTASimulationsResponse":{"properties":{"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of tournaments returned."},"tournaments":{"items":{"$ref":"#/components/schemas/WTATournamentSimulation"},"type":"array","title":"Tournaments"}},"type":"object","required":["count","tournaments"],"title":"WTASimulationsResponse"},"WTATournamentSimulation":{"properties":{"tournament":{"type":"string","title":"Tournament","description":"Tournament slug (e.g. `stuttgart`)."},"season":{"type":"string","title":"Season","description":"Season, 4-digit year for tennis."},"tournament_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tournament Type","description":"ATP: grand_slam | masters_1000 | atp_500 | atp_250. WTA: grand_slam | wta_1000 | wta_500 | wta_250."},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"hard | clay | grass."},"run_date":{"type":"string","title":"Run Date","description":"Forecast snapshot date (YYYYMMDD). One snapshot per day."},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"Exact forecast generation timestamp (ISO 8601)."},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations","description":"Number of simulated tournaments behind the probabilities."},"n_rounds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Rounds","description":"Bracket depth (rounds)."},"n_completed_rounds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Completed Rounds","description":"Fully decided rounds at forecast time."},"is_completed":{"type":"boolean","title":"Is Completed","description":"True once the tournament has a champion.","default":false},"champion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Champion","description":"Champion name, when the tournament is finished."},"players":{"items":{"$ref":"#/components/schemas/WTAPlayerForecast"},"type":"array","title":"Players","description":"Sorted by p_title descending."}},"type":"object","required":["tournament","season","run_date"],"title":"WTATournamentSimulation"},"WTAUpcomingMatch":{"properties":{"match_id":{"type":"string","title":"Match Id"},"sport":{"type":"string","enum":["wta"],"const":"wta","title":"Sport","default":"wta"},"date":{"type":"string","format":"date","title":"Date","description":"Tournament-scheduled START day, NOT the actual day of play. Shared by every match of the same tournament across all rounds. Do not interpret a value in the past as 'match already played' — tournaments typically run 7–14 days from this date."},"season":{"type":"string","title":"Season"},"tournament":{"$ref":"#/components/schemas/TournamentInfo"},"player_one":{"$ref":"#/components/schemas/PlayerStats","description":"First player in as-observed order. NOT winner-first canonicalised. All player-relative predictions are aligned to this ordering."},"player_two":{"$ref":"#/components/schemas/PlayerStats"},"predicted_total_games":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Total Games","description":"Rounded mean of the Poisson total-games regressor. Use the match-detail endpoint for the un-rounded mean and the full distribution. Independent of the winner classifier."},"predicted_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicted Result","description":"Player name predicted to win (winner classifier output)."}},"type":"object","required":["match_id","date","season","tournament","player_one","player_two"],"title":"WTAUpcomingMatch","description":"One upcoming WTA (women's tennis) match.\n\nIdentical contract to the ATP ``tennis`` sport: seven independent\nmodels, tournament-start ``date`` semantics, and the as-observed\nplayer order (never winner-first canonicalised). Every WTA match —\nGrand Slams included — is best-of-three sets. Audit each model via\n``GET /v1/calibration/wta/<model_id>``; discover ids via\n``GET /v1/models?sport=wta``."},"WTAUpcomingResponse":{"properties":{"matches":{"items":{"$ref":"#/components/schemas/WTAUpcomingMatch"},"type":"array","title":"Matches"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["matches","count"],"title":"WTAUpcomingResponse"}}}}