{
  "openapi": "3.1.0",
  "info": {
    "title": "VS Engine API",
    "description": "Central Stats & AI Content Motor powering Insideformation client products. Sources: Kambi odds + betting patterns, ValueStats insights, SportRadar match data. Data policy: **never invent data** — missing values become `null` + `blocked: true`, frontends hide blocked widgets. See /docs/concepts/data-policy.md.",
    "version": "1.0.0",
    "contact": {
      "name": "Insideformation — Johan Bryld",
      "email": "johan.f.bryld@gmail.com"
    },
    "license": {
      "name": "Proprietary — all rights reserved",
      "url": "https://insideformation.com"
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Per-client API key delivered out-of-band by Insideformation. Required on every protected endpoint; open endpoints (/status, /widgets) ignore it but still accept it."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": true,
        "properties": {
          "error": {
            "type": "string",
            "description": "Short machine-friendly error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error detail"
          }
        }
      },
      "MatchCard": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "eventId": {
            "type": "string",
            "description": "Kambi event ID"
          },
          "homeTeam": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "badge": {
                "type": "string"
              },
              "form": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "awayTeam": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "badge": {
                "type": "string"
              },
              "form": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "league": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Stable league key, e.g. \"premier_league\""
              },
              "name": {
                "type": "string",
                "description": "Human-readable league name from Kambi"
              }
            }
          },
          "kickoff": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "live",
              "finished"
            ]
          },
          "odds": {
            "type": "object",
            "nullable": true,
            "description": "1X2 + O/U 2.5 + BTTS summary odds from Kambi",
            "additionalProperties": true
          },
          "asianHandicap": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "marketCount": {
            "type": "integer"
          },
          "mostBacked": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          }
        },
        "title": "MatchCard"
      },
      "SportEventMapping": {
        "type": "object",
        "required": [
          "kambiEventId",
          "sportEventUrn",
          "homeTeamUrn",
          "awayTeamUrn",
          "homeTeamName",
          "awayTeamName",
          "kickoff",
          "league"
        ],
        "properties": {
          "kambiEventId": {
            "type": "string",
            "description": "Numeric Kambi event id, rendered as a string to avoid JS integer truncation",
            "example": "1024044217"
          },
          "sportEventUrn": {
            "type": "string",
            "description": "Canonical SportRadar sport_event URN, of the form sr:sport_event:<id>",
            "example": "sr:sport_event:61301151"
          },
          "homeTeamUrn": {
            "type": "string",
            "description": "SR competitor URN for the home team",
            "example": "sr:competitor:44"
          },
          "awayTeamUrn": {
            "type": "string",
            "description": "SR competitor URN for the away team",
            "example": "sr:competitor:43"
          },
          "homeTeamName": {
            "type": "string",
            "example": "Liverpool"
          },
          "awayTeamName": {
            "type": "string",
            "example": "Fulham"
          },
          "kickoff": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled kickoff in ISO 8601 UTC",
            "example": "2026-04-11T16:30:00Z"
          },
          "league": {
            "type": "string",
            "description": "Human-readable league name as supplied by Kambi",
            "example": "Premier League"
          }
        }
      },
      "MappingOkResponse": {
        "type": "object",
        "required": [
          "status",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ],
            "description": "Literal \"ok\" on success"
          },
          "data": {
            "$ref": "#/components/schemas/def-1"
          }
        }
      },
      "LiveRailResponse": {
        "type": "object",
        "required": [
          "matches",
          "meta"
        ],
        "properties": {
          "matches": {
            "type": "array",
            "description": "Up to `limit` currently-live matches in Tier A+B, sorted tier → clock desc → league id.",
            "items": {
              "type": "object",
              "required": [
                "eventId",
                "league",
                "homeTeam",
                "awayTeam",
                "state",
                "liveScore",
                "liveMinute",
                "odds",
                "fetchedAt",
                "provenance"
              ],
              "properties": {
                "eventId": {
                  "type": "integer",
                  "description": "Kambi numeric event id",
                  "example": 1024182402
                },
                "league": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "slug"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "bundesliga"
                    },
                    "name": {
                      "type": "string",
                      "example": "Bundesliga"
                    },
                    "slug": {
                      "type": "string",
                      "example": "bundesliga"
                    },
                    "countryCode": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO 3166-1 alpha-2 country code, or null for international competitions",
                      "example": "DE"
                    }
                  }
                },
                "homeTeam": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "example": "VfL Wolfsburg"
                    },
                    "shortName": {
                      "type": "string",
                      "nullable": true
                    },
                    "badge": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                },
                "awayTeam": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "example": "Eintracht Frankfurt"
                    },
                    "shortName": {
                      "type": "string",
                      "nullable": true
                    },
                    "badge": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "LIVE"
                  ],
                  "description": "Always \"LIVE\" for entries in this endpoint"
                },
                "liveScore": {
                  "type": "object",
                  "required": [
                    "home",
                    "away"
                  ],
                  "properties": {
                    "home": {
                      "type": "integer",
                      "example": 0
                    },
                    "away": {
                      "type": "integer",
                      "example": 2
                    }
                  }
                },
                "liveMinute": {
                  "type": "object",
                  "required": [
                    "display"
                  ],
                  "properties": {
                    "value": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Whole minutes parsed from SR clock; null for HT/FT/unparseable"
                    },
                    "display": {
                      "type": "string",
                      "description": "Render verbatim. Examples: \"85'\", \"45+2'\", \"HT\", \"FT\"",
                      "example": "85'"
                    }
                  }
                },
                "period": {
                  "type": "string",
                  "nullable": true,
                  "description": "Coarse period label derived from SR match_status. One of: 1H, 2H, HT, FT, ET, PEN — or null when unknown.",
                  "example": "2H"
                },
                "odds": {
                  "type": "object",
                  "required": [
                    "match1x2"
                  ],
                  "properties": {
                    "match1x2": {
                      "type": "object",
                      "required": [
                        "home",
                        "draw",
                        "away"
                      ],
                      "description": "Kambi milliodds (decimal × 1000). e.g. 1580 = 1.58",
                      "properties": {
                        "home": {
                          "type": "integer",
                          "example": 476000
                        },
                        "draw": {
                          "type": "integer",
                          "example": 26000
                        },
                        "away": {
                          "type": "integer",
                          "example": 1010
                        }
                      }
                    }
                  }
                },
                "fetchedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "ISO 8601 — freshness of the Kambi odds snapshot for this match"
                },
                "provenance": {
                  "type": "object",
                  "required": [
                    "origin",
                    "algorithm",
                    "generatedAt"
                  ],
                  "description": "Standard VS Engine provenance block — Kambi + SR origins + algorithm",
                  "properties": {
                    "origin": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "type",
                          "provider"
                        ],
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "api",
                              "file",
                              "derived"
                            ]
                          },
                          "provider": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "urn": {
                            "type": "string",
                            "nullable": true
                          },
                          "field": {
                            "type": "string"
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "algorithm": {
                      "type": "object",
                      "required": [
                        "description",
                        "file"
                      ],
                      "properties": {
                        "description": {
                          "type": "string"
                        },
                        "file": {
                          "type": "string"
                        },
                        "parameters": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "total",
              "limit",
              "sport",
              "majorLeaguesOnly",
              "refreshedAt"
            ],
            "properties": {
              "total": {
                "type": "integer",
                "description": "Number of matches returned. Can be less than `limit`; 0 when no live matches match the filter.",
                "example": 8
              },
              "limit": {
                "type": "integer",
                "description": "The effective cap applied to the response (1-20, default 10)",
                "example": 10
              },
              "sport": {
                "type": "string",
                "description": "Echo of the `sport` query param. Only `football` returns non-empty results.",
                "example": "football"
              },
              "majorLeaguesOnly": {
                "type": "boolean",
                "description": "Always `true` at launch — only Tier A+B leagues are considered.",
                "enum": [
                  true
                ]
              },
              "refreshedAt": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 — when this aggregator run happened"
              }
            }
          }
        }
      },
      "MinedPattern": {
        "type": "object",
        "required": [
          "conditionCategory",
          "conditionKey",
          "conditionParams",
          "outcomeKey",
          "hits",
          "sampleSize",
          "hitRate",
          "baseline",
          "surpriseDelta",
          "pValueRaw",
          "pValueCorrected",
          "surpriseScore",
          "rank",
          "appliesToday"
        ],
        "properties": {
          "conditionCategory": {
            "type": "string",
            "enum": [
              "h2h",
              "form",
              "trajectory",
              "player",
              "travel",
              "season_phase",
              "international_break"
            ],
            "description": "Broad bucket — drives which rendering template the shape layer picks."
          },
          "conditionKey": {
            "type": "string",
            "description": "Stable machine key for i18n template lookup, e.g. \"h2h_dominance\" or \"player_starts\".",
            "example": "h2h_dominance"
          },
          "conditionParams": {
            "type": "object",
            "additionalProperties": true,
            "description": "Structured params for template substitution (team names, distances, player names, etc.)."
          },
          "outcomeKey": {
            "type": "string",
            "description": "Stable outcome key, e.g. \"home_win\", \"late_winner_after_80\", \"btts\".",
            "example": "home_win"
          },
          "hits": {
            "type": "integer",
            "description": "Successes observed (out of sampleSize)."
          },
          "sampleSize": {
            "type": "integer",
            "description": "Total trials."
          },
          "hitRate": {
            "type": "number",
            "description": "hits / sampleSize, in [0, 1]."
          },
          "baseline": {
            "type": "number",
            "description": "Population baseline rate for that outcome, in [0, 1]."
          },
          "surpriseDelta": {
            "type": "number",
            "description": "hitRate - baseline (signed). |delta| >= 0.20 to surface."
          },
          "pValueRaw": {
            "type": "number",
            "description": "One-sided binomial p-value before correction."
          },
          "pValueCorrected": {
            "type": "number",
            "description": "Bonferroni-adjusted p-value, capped at 1.0. Required <= 0.05."
          },
          "surpriseScore": {
            "type": "number",
            "description": "|delta| * sqrt(n) * log(n). Used for ranking."
          },
          "rank": {
            "type": "integer",
            "description": "1 = most surprising; assigned post-sort."
          },
          "appliesToday": {
            "type": "boolean",
            "description": "True when the pattern's conditions hold for today's specific matchup (e.g. player is in starting XI)."
          },
          "subjectTeam": {
            "type": "string",
            "enum": [
              "home",
              "away"
            ],
            "description": "Which team the pattern applies to, when team-specific."
          },
          "textTemplate": {
            "type": "string",
            "description": "Optional rendering hint for the shape layer. Not a finished string — i18n owns the final copy."
          }
        }
      },
      "CachedInsights": {
        "type": "object",
        "required": [
          "generatedAt",
          "eventId",
          "totalTestsRun",
          "patternsAfterSignificance",
          "patternsAfterDedup",
          "patterns"
        ],
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp of when this mining run completed."
          },
          "eventId": {
            "type": "string",
            "description": "Kambi numeric event id (string-encoded) for the upcoming match.",
            "example": "1026050829"
          },
          "totalTestsRun": {
            "type": "integer",
            "description": "How many candidate patterns were statistically tested in this run."
          },
          "patternsAfterSignificance": {
            "type": "integer",
            "description": "Survived Bonferroni-corrected p < 0.05."
          },
          "patternsAfterDedup": {
            "type": "integer",
            "description": "Survived dedup against ValueStats — these are the patterns surfaced."
          },
          "patterns": {
            "type": "array",
            "description": "Up to top-25 mined patterns ranked by surpriseScore desc.",
            "items": {
              "$ref": "#/components/schemas/def-4"
            }
          }
        }
      },
      "TeamAggregates": {
        "type": "object",
        "required": [
          "teamId",
          "teamName",
          "season",
          "computedAt",
          "matchesAnalyzed"
        ],
        "properties": {
          "teamId": {
            "type": "string",
            "example": "sr:competitor:1764"
          },
          "teamName": {
            "type": "string",
            "example": "AIK"
          },
          "season": {
            "type": "string",
            "nullable": true,
            "example": "sr:season:138196"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "matchesAnalyzed": {
            "type": "integer",
            "example": 5
          },
          "corners": {
            "type": "object",
            "properties": {
              "for_per_match": {
                "type": "number",
                "nullable": true
              },
              "against_per_match": {
                "type": "number",
                "nullable": true
              },
              "home_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "away_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "home_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "away_against_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "cards": {
            "type": "object",
            "properties": {
              "yellow_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "red_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "red_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_when_leading_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_when_trailing_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "fouls": {
            "type": "object",
            "properties": {
              "committed_per_match": {
                "type": "number",
                "nullable": true
              },
              "drawn_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "free_kicks": {
            "type": "object",
            "properties": {
              "won_per_match": {
                "type": "number",
                "nullable": true
              },
              "conceded_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "penalties": {
            "type": "object",
            "properties": {
              "for": {
                "type": "integer",
                "nullable": true
              },
              "against": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "goals": {
            "type": "object",
            "properties": {
              "scored_per_match": {
                "type": "number",
                "nullable": true
              },
              "conceded_per_match": {
                "type": "number",
                "nullable": true
              },
              "by_interval": {
                "type": "object",
                "description": "Goals scored / conceded per 15-min interval. All nested values null when timeline missing.",
                "additionalProperties": true
              },
              "first_half_pct": {
                "type": "number",
                "nullable": true
              },
              "second_half_pct": {
                "type": "number",
                "nullable": true
              },
              "set_piece_goals": {
                "type": "integer",
                "nullable": true
              },
              "open_play_goals": {
                "type": "integer",
                "nullable": true
              },
              "penalty_goals": {
                "type": "integer",
                "nullable": true
              },
              "late_winner_count": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "match_state": {
            "type": "object",
            "properties": {
              "win_pct_when_leading_at_ht": {
                "type": "number",
                "nullable": true
              },
              "comeback_win_count": {
                "type": "integer",
                "nullable": true
              },
              "clean_sheet_pct": {
                "type": "number",
                "nullable": true
              },
              "clean_sheet_home_pct": {
                "type": "number",
                "nullable": true
              },
              "clean_sheet_away_pct": {
                "type": "number",
                "nullable": true
              },
              "btts_pct": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "form_context": {
            "type": "object",
            "properties": {
              "rest_days_before_last_match": {
                "type": "number",
                "nullable": true
              },
              "matches_in_last_30_days": {
                "type": "integer"
              },
              "win_pct_with_short_rest_lt_4d": {
                "type": "number",
                "nullable": true
              },
              "win_pct_with_long_rest_gt_7d": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "source": {
            "type": "string"
          }
        }
      },
      "PlayerAggregates": {
        "type": "object",
        "required": [
          "playerId",
          "playerName",
          "teamId",
          "season",
          "computedAt",
          "matches_played"
        ],
        "properties": {
          "playerId": {
            "type": "string",
            "example": "sr:player:869030"
          },
          "playerName": {
            "type": "string",
            "example": "Olusanya, Bénie"
          },
          "teamId": {
            "type": "string",
            "example": "sr:competitor:1759"
          },
          "season": {
            "type": "string",
            "nullable": true,
            "example": "sr:season:138196"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "matches_played": {
            "type": "integer"
          },
          "minutes_total": {
            "type": "integer",
            "nullable": true
          },
          "starts": {
            "type": "integer"
          },
          "subbed_on": {
            "type": "integer"
          },
          "subbed_off": {
            "type": "integer"
          },
          "attacking_per_90": {
            "type": "object",
            "properties": {
              "goals": {
                "type": "number",
                "nullable": true
              },
              "assists": {
                "type": "number",
                "nullable": true
              },
              "shots": {
                "type": "number",
                "nullable": true
              },
              "shots_on_target": {
                "type": "number",
                "nullable": true
              },
              "key_passes": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "attacking_totals": {
            "type": "object",
            "properties": {
              "goals": {
                "type": "integer"
              },
              "assists": {
                "type": "integer"
              },
              "shots": {
                "type": "integer"
              },
              "shots_on_target": {
                "type": "integer"
              }
            }
          },
          "efficiency": {
            "type": "object",
            "properties": {
              "goals_per_shot": {
                "type": "number",
                "nullable": true
              },
              "shots_on_target_pct": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "defensive_per_90": {
            "type": "object",
            "properties": {
              "tackles": {
                "type": "number",
                "nullable": true
              },
              "interceptions": {
                "type": "number",
                "nullable": true
              },
              "fouls_committed": {
                "type": "number",
                "nullable": true
              },
              "fouls_drawn": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "discipline": {
            "type": "object",
            "properties": {
              "yellow_cards": {
                "type": "integer"
              },
              "red_cards": {
                "type": "integer"
              },
              "yellow_per_match": {
                "type": "number",
                "nullable": true
              },
              "suspension_watch": {
                "type": "boolean"
              }
            }
          },
          "form": {
            "type": "object",
            "properties": {
              "goals_last_5": {
                "type": "integer"
              },
              "assists_last_5": {
                "type": "integer"
              },
              "shots_last_5": {
                "type": "integer"
              },
              "scoring_streak_matches": {
                "type": "integer"
              }
            }
          },
          "source": {
            "type": "string"
          }
        }
      },
      "def-0": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": true,
        "properties": {
          "error": {
            "type": "string",
            "description": "Short machine-friendly error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error detail"
          }
        },
        "title": "ErrorResponse"
      },
      "def-1": {
        "type": "object",
        "required": [
          "kambiEventId",
          "sportEventUrn",
          "homeTeamUrn",
          "awayTeamUrn",
          "homeTeamName",
          "awayTeamName",
          "kickoff",
          "league"
        ],
        "properties": {
          "kambiEventId": {
            "type": "string",
            "description": "Numeric Kambi event id, rendered as a string to avoid JS integer truncation",
            "example": "1024044217"
          },
          "sportEventUrn": {
            "type": "string",
            "description": "Canonical SportRadar sport_event URN, of the form sr:sport_event:<id>",
            "example": "sr:sport_event:61301151"
          },
          "homeTeamUrn": {
            "type": "string",
            "description": "SR competitor URN for the home team",
            "example": "sr:competitor:44"
          },
          "awayTeamUrn": {
            "type": "string",
            "description": "SR competitor URN for the away team",
            "example": "sr:competitor:43"
          },
          "homeTeamName": {
            "type": "string",
            "example": "Liverpool"
          },
          "awayTeamName": {
            "type": "string",
            "example": "Fulham"
          },
          "kickoff": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled kickoff in ISO 8601 UTC",
            "example": "2026-04-11T16:30:00Z"
          },
          "league": {
            "type": "string",
            "description": "Human-readable league name as supplied by Kambi",
            "example": "Premier League"
          }
        },
        "title": "SportEventMapping"
      },
      "def-2": {
        "type": "object",
        "required": [
          "status",
          "data"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ],
            "description": "Literal \"ok\" on success"
          },
          "data": {
            "$ref": "#/components/schemas/def-1"
          }
        },
        "title": "MappingOkResponse"
      },
      "def-3": {
        "type": "object",
        "required": [
          "matches",
          "meta"
        ],
        "properties": {
          "matches": {
            "type": "array",
            "description": "Up to `limit` currently-live matches in Tier A+B, sorted tier → clock desc → league id.",
            "items": {
              "type": "object",
              "required": [
                "eventId",
                "league",
                "homeTeam",
                "awayTeam",
                "state",
                "liveScore",
                "liveMinute",
                "odds",
                "fetchedAt",
                "provenance"
              ],
              "properties": {
                "eventId": {
                  "type": "integer",
                  "description": "Kambi numeric event id",
                  "example": 1024182402
                },
                "league": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "slug"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "example": "bundesliga"
                    },
                    "name": {
                      "type": "string",
                      "example": "Bundesliga"
                    },
                    "slug": {
                      "type": "string",
                      "example": "bundesliga"
                    },
                    "countryCode": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO 3166-1 alpha-2 country code, or null for international competitions",
                      "example": "DE"
                    }
                  }
                },
                "homeTeam": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "example": "VfL Wolfsburg"
                    },
                    "shortName": {
                      "type": "string",
                      "nullable": true
                    },
                    "badge": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                },
                "awayTeam": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "example": "Eintracht Frankfurt"
                    },
                    "shortName": {
                      "type": "string",
                      "nullable": true
                    },
                    "badge": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "LIVE"
                  ],
                  "description": "Always \"LIVE\" for entries in this endpoint"
                },
                "liveScore": {
                  "type": "object",
                  "required": [
                    "home",
                    "away"
                  ],
                  "properties": {
                    "home": {
                      "type": "integer",
                      "example": 0
                    },
                    "away": {
                      "type": "integer",
                      "example": 2
                    }
                  }
                },
                "liveMinute": {
                  "type": "object",
                  "required": [
                    "display"
                  ],
                  "properties": {
                    "value": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Whole minutes parsed from SR clock; null for HT/FT/unparseable"
                    },
                    "display": {
                      "type": "string",
                      "description": "Render verbatim. Examples: \"85'\", \"45+2'\", \"HT\", \"FT\"",
                      "example": "85'"
                    }
                  }
                },
                "period": {
                  "type": "string",
                  "nullable": true,
                  "description": "Coarse period label derived from SR match_status. One of: 1H, 2H, HT, FT, ET, PEN — or null when unknown.",
                  "example": "2H"
                },
                "odds": {
                  "type": "object",
                  "required": [
                    "match1x2"
                  ],
                  "properties": {
                    "match1x2": {
                      "type": "object",
                      "required": [
                        "home",
                        "draw",
                        "away"
                      ],
                      "description": "Kambi milliodds (decimal × 1000). e.g. 1580 = 1.58",
                      "properties": {
                        "home": {
                          "type": "integer",
                          "example": 476000
                        },
                        "draw": {
                          "type": "integer",
                          "example": 26000
                        },
                        "away": {
                          "type": "integer",
                          "example": 1010
                        }
                      }
                    }
                  }
                },
                "fetchedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "ISO 8601 — freshness of the Kambi odds snapshot for this match"
                },
                "provenance": {
                  "type": "object",
                  "required": [
                    "origin",
                    "algorithm",
                    "generatedAt"
                  ],
                  "description": "Standard VS Engine provenance block — Kambi + SR origins + algorithm",
                  "properties": {
                    "origin": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "type",
                          "provider"
                        ],
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "api",
                              "file",
                              "derived"
                            ]
                          },
                          "provider": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "urn": {
                            "type": "string",
                            "nullable": true
                          },
                          "field": {
                            "type": "string"
                          },
                          "note": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "algorithm": {
                      "type": "object",
                      "required": [
                        "description",
                        "file"
                      ],
                      "properties": {
                        "description": {
                          "type": "string"
                        },
                        "file": {
                          "type": "string"
                        },
                        "parameters": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "total",
              "limit",
              "sport",
              "majorLeaguesOnly",
              "refreshedAt"
            ],
            "properties": {
              "total": {
                "type": "integer",
                "description": "Number of matches returned. Can be less than `limit`; 0 when no live matches match the filter.",
                "example": 8
              },
              "limit": {
                "type": "integer",
                "description": "The effective cap applied to the response (1-20, default 10)",
                "example": 10
              },
              "sport": {
                "type": "string",
                "description": "Echo of the `sport` query param. Only `football` returns non-empty results.",
                "example": "football"
              },
              "majorLeaguesOnly": {
                "type": "boolean",
                "description": "Always `true` at launch — only Tier A+B leagues are considered.",
                "enum": [
                  true
                ]
              },
              "refreshedAt": {
                "type": "string",
                "format": "date-time",
                "description": "ISO 8601 — when this aggregator run happened"
              }
            }
          }
        },
        "title": "LiveRailResponse"
      },
      "def-4": {
        "type": "object",
        "required": [
          "conditionCategory",
          "conditionKey",
          "conditionParams",
          "outcomeKey",
          "hits",
          "sampleSize",
          "hitRate",
          "baseline",
          "surpriseDelta",
          "pValueRaw",
          "pValueCorrected",
          "surpriseScore",
          "rank",
          "appliesToday"
        ],
        "properties": {
          "conditionCategory": {
            "type": "string",
            "enum": [
              "h2h",
              "form",
              "trajectory",
              "player",
              "travel",
              "season_phase",
              "international_break"
            ],
            "description": "Broad bucket — drives which rendering template the shape layer picks."
          },
          "conditionKey": {
            "type": "string",
            "description": "Stable machine key for i18n template lookup, e.g. \"h2h_dominance\" or \"player_starts\".",
            "example": "h2h_dominance"
          },
          "conditionParams": {
            "type": "object",
            "additionalProperties": true,
            "description": "Structured params for template substitution (team names, distances, player names, etc.)."
          },
          "outcomeKey": {
            "type": "string",
            "description": "Stable outcome key, e.g. \"home_win\", \"late_winner_after_80\", \"btts\".",
            "example": "home_win"
          },
          "hits": {
            "type": "integer",
            "description": "Successes observed (out of sampleSize)."
          },
          "sampleSize": {
            "type": "integer",
            "description": "Total trials."
          },
          "hitRate": {
            "type": "number",
            "description": "hits / sampleSize, in [0, 1]."
          },
          "baseline": {
            "type": "number",
            "description": "Population baseline rate for that outcome, in [0, 1]."
          },
          "surpriseDelta": {
            "type": "number",
            "description": "hitRate - baseline (signed). |delta| >= 0.20 to surface."
          },
          "pValueRaw": {
            "type": "number",
            "description": "One-sided binomial p-value before correction."
          },
          "pValueCorrected": {
            "type": "number",
            "description": "Bonferroni-adjusted p-value, capped at 1.0. Required <= 0.05."
          },
          "surpriseScore": {
            "type": "number",
            "description": "|delta| * sqrt(n) * log(n). Used for ranking."
          },
          "rank": {
            "type": "integer",
            "description": "1 = most surprising; assigned post-sort."
          },
          "appliesToday": {
            "type": "boolean",
            "description": "True when the pattern's conditions hold for today's specific matchup (e.g. player is in starting XI)."
          },
          "subjectTeam": {
            "type": "string",
            "enum": [
              "home",
              "away"
            ],
            "description": "Which team the pattern applies to, when team-specific."
          },
          "textTemplate": {
            "type": "string",
            "description": "Optional rendering hint for the shape layer. Not a finished string — i18n owns the final copy."
          }
        },
        "title": "MinedPattern"
      },
      "def-5": {
        "type": "object",
        "required": [
          "generatedAt",
          "eventId",
          "totalTestsRun",
          "patternsAfterSignificance",
          "patternsAfterDedup",
          "patterns"
        ],
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp of when this mining run completed."
          },
          "eventId": {
            "type": "string",
            "description": "Kambi numeric event id (string-encoded) for the upcoming match.",
            "example": "1026050829"
          },
          "totalTestsRun": {
            "type": "integer",
            "description": "How many candidate patterns were statistically tested in this run."
          },
          "patternsAfterSignificance": {
            "type": "integer",
            "description": "Survived Bonferroni-corrected p < 0.05."
          },
          "patternsAfterDedup": {
            "type": "integer",
            "description": "Survived dedup against ValueStats — these are the patterns surfaced."
          },
          "patterns": {
            "type": "array",
            "description": "Up to top-25 mined patterns ranked by surpriseScore desc.",
            "items": {
              "$ref": "#/components/schemas/def-4"
            }
          }
        },
        "title": "CachedInsights"
      },
      "def-6": {
        "type": "object",
        "required": [
          "teamId",
          "teamName",
          "season",
          "computedAt",
          "matchesAnalyzed"
        ],
        "properties": {
          "teamId": {
            "type": "string",
            "example": "sr:competitor:1764"
          },
          "teamName": {
            "type": "string",
            "example": "AIK"
          },
          "season": {
            "type": "string",
            "nullable": true,
            "example": "sr:season:138196"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "matchesAnalyzed": {
            "type": "integer",
            "example": 5
          },
          "corners": {
            "type": "object",
            "properties": {
              "for_per_match": {
                "type": "number",
                "nullable": true
              },
              "against_per_match": {
                "type": "number",
                "nullable": true
              },
              "home_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "away_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "home_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "away_against_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "cards": {
            "type": "object",
            "properties": {
              "yellow_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "red_for_per_match": {
                "type": "number",
                "nullable": true
              },
              "red_against_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_when_leading_per_match": {
                "type": "number",
                "nullable": true
              },
              "yellow_when_trailing_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "fouls": {
            "type": "object",
            "properties": {
              "committed_per_match": {
                "type": "number",
                "nullable": true
              },
              "drawn_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "free_kicks": {
            "type": "object",
            "properties": {
              "won_per_match": {
                "type": "number",
                "nullable": true
              },
              "conceded_per_match": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "penalties": {
            "type": "object",
            "properties": {
              "for": {
                "type": "integer",
                "nullable": true
              },
              "against": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "goals": {
            "type": "object",
            "properties": {
              "scored_per_match": {
                "type": "number",
                "nullable": true
              },
              "conceded_per_match": {
                "type": "number",
                "nullable": true
              },
              "by_interval": {
                "type": "object",
                "description": "Goals scored / conceded per 15-min interval. All nested values null when timeline missing.",
                "additionalProperties": true
              },
              "first_half_pct": {
                "type": "number",
                "nullable": true
              },
              "second_half_pct": {
                "type": "number",
                "nullable": true
              },
              "set_piece_goals": {
                "type": "integer",
                "nullable": true
              },
              "open_play_goals": {
                "type": "integer",
                "nullable": true
              },
              "penalty_goals": {
                "type": "integer",
                "nullable": true
              },
              "late_winner_count": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "match_state": {
            "type": "object",
            "properties": {
              "win_pct_when_leading_at_ht": {
                "type": "number",
                "nullable": true
              },
              "comeback_win_count": {
                "type": "integer",
                "nullable": true
              },
              "clean_sheet_pct": {
                "type": "number",
                "nullable": true
              },
              "clean_sheet_home_pct": {
                "type": "number",
                "nullable": true
              },
              "clean_sheet_away_pct": {
                "type": "number",
                "nullable": true
              },
              "btts_pct": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "form_context": {
            "type": "object",
            "properties": {
              "rest_days_before_last_match": {
                "type": "number",
                "nullable": true
              },
              "matches_in_last_30_days": {
                "type": "integer"
              },
              "win_pct_with_short_rest_lt_4d": {
                "type": "number",
                "nullable": true
              },
              "win_pct_with_long_rest_gt_7d": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "source": {
            "type": "string"
          }
        },
        "title": "TeamAggregates"
      },
      "def-7": {
        "type": "object",
        "required": [
          "playerId",
          "playerName",
          "teamId",
          "season",
          "computedAt",
          "matches_played"
        ],
        "properties": {
          "playerId": {
            "type": "string",
            "example": "sr:player:869030"
          },
          "playerName": {
            "type": "string",
            "example": "Olusanya, Bénie"
          },
          "teamId": {
            "type": "string",
            "example": "sr:competitor:1759"
          },
          "season": {
            "type": "string",
            "nullable": true,
            "example": "sr:season:138196"
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "matches_played": {
            "type": "integer"
          },
          "minutes_total": {
            "type": "integer",
            "nullable": true
          },
          "starts": {
            "type": "integer"
          },
          "subbed_on": {
            "type": "integer"
          },
          "subbed_off": {
            "type": "integer"
          },
          "attacking_per_90": {
            "type": "object",
            "properties": {
              "goals": {
                "type": "number",
                "nullable": true
              },
              "assists": {
                "type": "number",
                "nullable": true
              },
              "shots": {
                "type": "number",
                "nullable": true
              },
              "shots_on_target": {
                "type": "number",
                "nullable": true
              },
              "key_passes": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "attacking_totals": {
            "type": "object",
            "properties": {
              "goals": {
                "type": "integer"
              },
              "assists": {
                "type": "integer"
              },
              "shots": {
                "type": "integer"
              },
              "shots_on_target": {
                "type": "integer"
              }
            }
          },
          "efficiency": {
            "type": "object",
            "properties": {
              "goals_per_shot": {
                "type": "number",
                "nullable": true
              },
              "shots_on_target_pct": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "defensive_per_90": {
            "type": "object",
            "properties": {
              "tackles": {
                "type": "number",
                "nullable": true
              },
              "interceptions": {
                "type": "number",
                "nullable": true
              },
              "fouls_committed": {
                "type": "number",
                "nullable": true
              },
              "fouls_drawn": {
                "type": "number",
                "nullable": true
              }
            }
          },
          "discipline": {
            "type": "object",
            "properties": {
              "yellow_cards": {
                "type": "integer"
              },
              "red_cards": {
                "type": "integer"
              },
              "yellow_per_match": {
                "type": "number",
                "nullable": true
              },
              "suspension_watch": {
                "type": "boolean"
              }
            }
          },
          "form": {
            "type": "object",
            "properties": {
              "goals_last_5": {
                "type": "integer"
              },
              "assists_last_5": {
                "type": "integer"
              },
              "shots_last_5": {
                "type": "integer"
              },
              "scoring_streak_matches": {
                "type": "integer"
              }
            }
          },
          "source": {
            "type": "string"
          }
        },
        "title": "PlayerAggregates"
      }
    }
  },
  "paths": {
    "/feed/{clientId}": {
      "get": {
        "summary": "GET /dataengine/feed/:clientId",
        "tags": [
          "lobby"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/feed/{clientId}/poll": {
      "get": {
        "summary": "GET /dataengine/feed/:clientId/poll",
        "tags": [
          "lobby"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/feed/{clientId}/match/{eventId}": {
      "get": {
        "summary": "GET /dataengine/feed/:clientId/match/:eventId",
        "tags": [
          "match-detail"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/feed/{clientId}/refresh": {
      "post": {
        "summary": "POST /dataengine/feed/:clientId/refresh",
        "tags": [
          "lobby"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/articles/generate": {
      "post": {
        "summary": "POST /dataengine/articles/generate",
        "tags": [
          "content"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/video/generate": {
      "post": {
        "summary": "POST /dataengine/video/generate",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/video/{videoId}": {
      "get": {
        "summary": "GET /dataengine/video/:videoId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "videoId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/video/by-match/{eventId}": {
      "get": {
        "summary": "GET /dataengine/video/by-match/:eventId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/pipeline/run": {
      "post": {
        "summary": "POST /dataengine/pipeline/run",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/recipes": {
      "get": {
        "summary": "GET /dataengine/recipes",
        "tags": [
          "content"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/recipes/reload": {
      "post": {
        "summary": "POST /dataengine/recipes/reload",
        "tags": [
          "content"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/auto-articles/status": {
      "get": {
        "summary": "GET /dataengine/auto-articles/status",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/auto-articles/run": {
      "post": {
        "summary": "POST /dataengine/auto-articles/run",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/auto-videos/status": {
      "get": {
        "summary": "GET /dataengine/auto-videos/status",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/auto-videos/run": {
      "post": {
        "summary": "POST /dataengine/auto-videos/run",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/pipeline/match/{eventId}": {
      "get": {
        "summary": "Per-event pipeline attempt history",
        "tags": [
          "admin"
        ],
        "description": "Returns the last 50 article/video generation attempts for a single match event, each annotated with timestamp, recipe, status, optional error type+message, and cost or generated content id when available. Useful for diagnosing silent failures (Claude credit issues, HeyGen outages, ElevenLabs/Imagen errors) without reading container stderr.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Kambi event id"
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "eventId",
                    "attempts"
                  ],
                  "properties": {
                    "eventId": {
                      "type": "string"
                    },
                    "clientId": {
                      "type": "string",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "timestamp",
                          "pipeline",
                          "recipe",
                          "status"
                        ],
                        "properties": {
                          "timestamp": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "pipeline": {
                            "type": "string",
                            "enum": [
                              "auto-articles",
                              "auto-videos"
                            ]
                          },
                          "recipe": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "success",
                              "failed",
                              "skipped"
                            ]
                          },
                          "clientId": {
                            "type": "string"
                          },
                          "error": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            }
                          },
                          "costEur": {
                            "type": "number"
                          },
                          "generatedArticleId": {
                            "type": "string"
                          },
                          "generatedVideoId": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "summary": "GET /dataengine/status",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/cost": {
      "get": {
        "summary": "GET /dataengine/cost",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/cost/log": {
      "get": {
        "summary": "GET /dataengine/cost/log",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/widgets": {
      "get": {
        "summary": "GET /dataengine/widgets",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/journalists": {
      "get": {
        "summary": "GET /dataengine/journalists",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/alerts": {
      "get": {
        "summary": "GET /dataengine/alerts",
        "tags": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/health/leagues": {
      "get": {
        "summary": "Per-league health snapshot (OK / empty / blocked)",
        "tags": [
          "admin"
        ],
        "description": "Reports which leagues are live, empty or blocked, with the age of the most recent match file per league. Open endpoint — no API key required. Added in the 2026-04-12 audit remediation (item N2).",
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/standalone/{leagueId}": {
      "get": {
        "summary": "GET /dataengine/standalone/:leagueId",
        "tags": [
          "league"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/standalone/{leagueId}/refresh": {
      "post": {
        "summary": "POST /dataengine/standalone/:leagueId/refresh",
        "tags": [
          "league"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/leaders/{leagueId}": {
      "get": {
        "summary": "GET /dataengine/leaders/:leagueId",
        "tags": [
          "league"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/goal-profiles": {
      "get": {
        "summary": "GET /dataengine/goal-profiles",
        "tags": [
          "league"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/team-stats-aggregates": {
      "get": {
        "summary": "GET /dataengine/team-stats-aggregates",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/player-profiles": {
      "get": {
        "summary": "GET /dataengine/player-profiles",
        "tags": [
          "league"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/liga-bast/{leagueId}": {
      "get": {
        "summary": "GET /dataengine/liga-bast/:leagueId",
        "tags": [
          "league"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/insights/{eventId}": {
      "get": {
        "summary": "Read cached pre-match mined insights for one fixture",
        "tags": [
          "insights"
        ],
        "description": "Returns the top-25 statistically-validated patterns mined for the given Kambi event. Patterns are mined every 6 hours pre-match by the cron at minutes :45 of hours 1, 7, 13 and 19 UTC, and cached on disk under `computed/insights/{eventId}`. The cached payload is served with a 60s Cache-Control max-age + ETag — clients should honor `If-None-Match` to avoid redundant transfers between mining ticks.\n\nThis dataset SUPPLEMENTS the editorial ValueStats insight feed — overlapping criteria are filtered server-side so consumers never see duplicates. Every pattern surfaced has survived: (1) sample size n >= 10, (2) signed |Δ| >= 20pp vs population baseline, and (3) Bonferroni-corrected one-sided binomial p < 0.05 across all tests run for this event.\n\nSet `?run=1` to force read-through mining if no cache exists yet — useful for editorial preview tooling. Returns 404 when the event has never been mined and `run` is unset.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "in": "query",
            "name": "run",
            "required": false,
            "description": "When set to `1`, mine on-demand if no cache exists."
          },
          {
            "schema": {
              "type": "string"
            },
            "example": "1026050829",
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id (string-encoded to avoid JS integer truncation)."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-5"
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/insights/{eventId}/run": {
      "post": {
        "summary": "Force re-mine insights for one fixture",
        "tags": [
          "insights"
        ],
        "description": "Idempotent re-mining of pre-match patterns for a single Kambi event. Reads the cached EnrichedMatch + historical SR summaries off disk, runs all four mining phases (scoring/dedup, h2h+form+trajectory, player splits, external signals), Bonferroni-corrects, dedups against ValueStats, and overwrites `computed/insights/{eventId}` with the fresh top-25.\n\nReturns the run summary — counts at each filter stage plus the top-3 patterns by surpriseScore. Use this for editorial preview after pushing new ValueStats data, or to manually warm a fixture that the 6h cron missed.\n\nMaster-key only (POST endpoints require X-API-Key per the global auth hook). Returns 404 when no EnrichedMatch is on disk for the event, 500 if mining throws (error message in response body — never fabricated).",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "example": "1026050829",
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id (string-encoded)."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "eventId",
                    "totalTestsRun",
                    "patternsAfterSignificance",
                    "patternsAfterDedup",
                    "surfaced",
                    "top3"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "eventId": {
                      "type": "string",
                      "example": "1026050829"
                    },
                    "totalTestsRun": {
                      "type": "integer"
                    },
                    "patternsAfterSignificance": {
                      "type": "integer"
                    },
                    "patternsAfterDedup": {
                      "type": "integer"
                    },
                    "surfaced": {
                      "type": "integer",
                      "description": "Number of patterns returned (post-dedup, capped at top-25)."
                    },
                    "top3": {
                      "type": "array",
                      "description": "Top 3 patterns by surpriseScore — quick preview without fetching the full set.",
                      "items": {
                        "$ref": "#/components/schemas/def-4"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          },
          "500": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/insights/run-all": {
      "post": {
        "summary": "Force re-mine insights for every upcoming pre-match fixture",
        "tags": [
          "insights"
        ],
        "description": "Iterates every EnrichedMatch on disk where `status === \"scheduled\"` and `kickoff` is in the future, and re-mines each one. Idempotent — safe to call repeatedly. Skips matches whose status, kickoff, or team ids fail validation; per-event mining errors are caught and reported (capped at the first 10) without aborting the run.\n\nThis is the same operation the 6-hourly cron (`45 1,7,13,19 * * *` UTC) performs — call this endpoint manually only for editorial bulk-warm or after a data backfill. Compute is local-only (no AI calls), so this endpoint is NOT subject to the `AI_DAILY_BUDGET_EUR` cost guard. Typical run-time is ~30s for ~300 matches against the existing on-disk historical cache.\n\nMaster-key only.",
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "scheduled",
                    "mined",
                    "skipped",
                    "failed",
                    "errors"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "scheduled": {
                      "type": "integer",
                      "description": "How many upcoming pre-match fixtures were attempted."
                    },
                    "mined": {
                      "type": "integer",
                      "description": "Successfully re-mined and cached."
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Skipped because not scheduled, kickoff in past, or missing team ids."
                    },
                    "failed": {
                      "type": "integer",
                      "description": "Mining threw — see `errors` for details (capped at 10)."
                    },
                    "errors": {
                      "type": "array",
                      "description": "First 10 errors with eventId + message.",
                      "items": {
                        "type": "object",
                        "required": [
                          "eventId",
                          "error"
                        ],
                        "properties": {
                          "eventId": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/catalog": {
      "get": {
        "summary": "Self-service API catalog (machine-readable JSON)",
        "tags": [
          "catalog"
        ],
        "description": "Returns a single document describing every endpoint, every widget, every dataset, the auth model, rate limits, and a recent-changes log. Designed for downstream consumers that need to discover capabilities without reading the source.\n\nThe endpoint list is harvested from the live Fastify route table at request time, so it can never drift from the running API. Memoized for 30 seconds.\n\nFor a browser-friendly version see `GET /dataengine/docs/catalog.html`. For the full OpenAPI 3.1 spec see `/dataengine/docs/openapi.yaml` or the interactive Scalar UI at `/dataengine/docs/`.",
        "responses": {
          "200": {
            "description": "Catalog document. See the response example for shape.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Catalog document. See the response example for shape."
                }
              }
            }
          }
        }
      }
    },
    "/docs/catalog.html": {
      "get": {
        "summary": "Self-service API catalog (browser-friendly HTML)",
        "tags": [
          "catalog"
        ],
        "description": "Same content as `GET /dataengine/catalog` but rendered as a self-contained HTML page with inline CSS — no external dependencies. Table-of-contents at top, sections for authentication, endpoints (grouped by tag), widgets, datasets, rate limits, and changelog.",
        "responses": {
          "200": {
            "description": "HTML document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML document."
                }
              }
            }
          }
        }
      }
    },
    "/admin/packages": {
      "get": {
        "summary": "List approval queue packages",
        "tags": [
          "admin-packages"
        ],
        "description": "Returns a compact list view of packages, sorted by kickoff descending. Default filter is `status=pending_approval, clientId=hemmaklubben, limit=50`.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending_approval",
                "approved",
                "scheduled",
                "live",
                "rejected",
                "dropped",
                "unpublished",
                "all"
              ]
            },
            "in": "query",
            "name": "status",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "clientId",
            "required": false
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "from",
            "required": false
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "in": "query",
            "name": "to",
            "required": false
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "limit",
            "required": false
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}": {
      "get": {
        "summary": "Fetch full package with version history",
        "tags": [
          "admin-packages"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}/version/{versionId}": {
      "get": {
        "summary": "Fetch a specific historical version of a package",
        "tags": [
          "admin-packages"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "versionId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}/approve": {
      "post": {
        "summary": "Approve a pending package",
        "tags": [
          "admin-packages"
        ],
        "description": "Body: `{ \"goLive\": \"now\" }` to publish immediately, or `{ \"goLive\": \"<ISO>\" }` to schedule. Returns 409 if the package is not in `pending_approval`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "goLive"
                ],
                "properties": {
                  "goLive": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}/reject": {
      "post": {
        "summary": "Reject the current version with feedback and trigger regeneration",
        "tags": [
          "admin-packages"
        ],
        "description": "Marks the current version as rejected, stores the feedback comment, and enqueues a regeneration via the relevant article/video pipeline. Subsequent generation lands as v(n+1) on the same package; status stays `pending_approval` throughout. v1 limitation: scope=`video.script` still triggers a full re-render of audio + video.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scope",
                  "comment"
                ],
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "all",
                      "article",
                      "article.heroImage",
                      "video",
                      "video.script",
                      "video.videoTrack",
                      "video.audioTrack",
                      "video.subtitles",
                      "bannerHeadlines"
                    ]
                  },
                  "comment": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}/edit": {
      "patch": {
        "summary": "Apply an in-place text edit to the current version",
        "tags": [
          "admin-packages"
        ],
        "description": "Field is a dot-path inside `currentVersion.elements`, e.g. \"article.headline\". Numeric path components index arrays (e.g. \"article.body.0.text\"). The edit is applied immediately to the package element snapshot AND mirrored to the canonical article/video file when the field maps cleanly. An entry is appended to `edits[]`. No new version is created and no regen is triggered.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "field",
                  "value"
                ],
                "properties": {
                  "field": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/admin/packages/{id}/unpublish": {
      "post": {
        "summary": "Unpublish a live package",
        "tags": [
          "admin-packages"
        ],
        "description": "Moves status from `live` to `unpublished`. Subsequent feed rebuilds drop it from the lobby.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/dam/{clientId}/catalog": {
      "get": {
        "summary": "GET /dataengine/dam/:clientId/catalog",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/dam/{clientId}/select": {
      "get": {
        "summary": "GET /dataengine/dam/:clientId/select",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/matches": {
      "get": {
        "summary": "GET /dataengine/fs/matches",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/match/{eventId}": {
      "get": {
        "summary": "GET /dataengine/fs/match/:eventId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/team/{teamId}": {
      "get": {
        "summary": "GET /dataengine/fs/team/:teamId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "teamId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/player/{playerId}": {
      "get": {
        "summary": "GET /dataengine/fs/player/:playerId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "playerId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/standings/{leagueId}": {
      "get": {
        "summary": "GET /dataengine/fs/standings/:leagueId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/fs/leaders/{leagueId}": {
      "get": {
        "summary": "GET /dataengine/fs/leaders/:leagueId",
        "tags": [
          "league"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "leagueId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/brand/{clientId}": {
      "get": {
        "summary": "GET /dataengine/brand/:clientId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/brand/{clientId}/invalidate": {
      "post": {
        "summary": "POST /dataengine/brand/:clientId/invalidate",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "clientId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/mappings/sr-to-kambi": {
      "get": {
        "summary": "Resolve a SportRadar sport_event URN to its Kambi event id",
        "tags": [
          "mappings"
        ],
        "description": "Takes a SportRadar `sr:sport_event:<id>` URN and returns the canonical mapping record (Kambi numeric event id + team URNs + kickoff + league) if an enriched match exists with that URN persisted.\n\nReturns 404 when the URN is not present on any enriched record. This happens when SR has not yet published the fixture in either team's summary cache, or when the enriched match pre-dates the sportEventUrn persistence rollout and has not been re-enriched. The engine **never fabricates a mapping** — a 404 is the honest answer per the no-invented-data policy.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^sr:sport_event:\\d+$"
            },
            "example": "sr:sport_event:61301151",
            "in": "query",
            "name": "urn",
            "required": true,
            "description": "SportRadar sport_event URN, e.g. `sr:sport_event:61301151`"
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-2"
                }
              }
            }
          },
          "400": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/mappings/kambi-to-sr": {
      "get": {
        "summary": "Resolve a Kambi event id to its SportRadar sport_event URN",
        "tags": [
          "mappings"
        ],
        "description": "Takes a numeric Kambi event id and returns the canonical mapping record (SR sport_event URN + team URNs + kickoff + league) if the enriched match has its `sportEventUrn` field populated.\n\nReturns 404 when no SR URN has been resolved for this Kambi event. Root causes: the match is not yet enriched, SR has not published the fixture in either team's summary cache (common for in-play matches — SR's summaries endpoint drops fixtures while they are in-play and republishes after full-time), or the match belongs to a league we do not ingest from SR (non-soccer leagues always 404). **Never fabricates a mapping.**",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "example": "1024044217",
            "in": "query",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id (passed as string to avoid JS integer truncation)"
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-2"
                }
              }
            }
          },
          "400": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/odds/{eventId}": {
      "get": {
        "summary": "GET /dataengine/odds/:eventId",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/odds/{eventId}/movement": {
      "get": {
        "summary": "GET /dataengine/odds/:eventId/movement",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "eventId",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/feed": {
      "get": {
        "summary": "GET /dataengine/junibet/feed",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/popular": {
      "get": {
        "summary": "GET /dataengine/junibet/popular",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/catalog": {
      "get": {
        "summary": "GET /dataengine/junibet/catalog",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/accumulators": {
      "get": {
        "summary": "GET /dataengine/junibet/accumulators",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/market-spotlight": {
      "get": {
        "summary": "GET /dataengine/junibet/market-spotlight",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/ticker": {
      "get": {
        "summary": "GET /dataengine/junibet/ticker",
        "tags": [
          "junibet-features"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/junibet/live-rail": {
      "get": {
        "summary": "Live Now rail — major-league in-play matches for the Junibet lobby",
        "tags": [
          "junibet-features"
        ],
        "description": "Returns up to 10 currently-live football matches from Tier A (Premier League, La Liga, Serie A, Bundesliga, Ligue 1) and Tier B (Champions, Europa, Conference League). Each entry carries the live score, SR clock, live 1X2 milliodds, and a full provenance block with both Kambi and SportRadar origins.\n\nEvery request re-aggregates from the raw Kambi (≤150s freshness budget) and raw SR live summary (≤30s freshness budget) caches, so response latency is O(matches) with no deep enrichment. Matches that fail any freshness or completeness check are silently dropped — the endpoint never ships a partial card with missing score or odds.\n\nPolled at 5 s cadence by the frontend. Returns HTTP 200 with an empty matches array when no Tier A/B matches are live — never 404.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "example": "10",
            "in": "query",
            "name": "limit",
            "required": false,
            "description": "Max number of matches (1-20, default 10)"
          },
          {
            "schema": {
              "type": "string"
            },
            "example": "football",
            "in": "query",
            "name": "sport",
            "required": false,
            "description": "Sport filter. Only `football` is implemented at launch; anything else returns an empty array."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-3"
                }
              }
            }
          }
        }
      }
    },
    "/client/{id}/sportsbook/feed": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/feed",
        "tags": [
          "lobby"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/popular": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/popular",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/catalog": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/catalog",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/accumulators": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/accumulators",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/market-spotlight": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/market-spotlight",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/ticker": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/ticker",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/client/{id}/sportsbook/live-rail": {
      "get": {
        "summary": "GET /dataengine/client/:id/sportsbook/live-rail",
        "tags": [
          "discovery"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "path",
            "name": "id",
            "required": true
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/live/{eventId}/summary": {
      "get": {
        "summary": "Fetch the latest SportRadar live summary for an in-play match",
        "tags": [
          "live"
        ],
        "description": "Returns the most recent `sport_event_status` (match_status, period, clock, home/away score, period_scores) and `statistics.totals` (possession, shots, corners, fouls, cards) that the live poller has harvested from SportRadar for this fixture, plus optional `ball_locations` and `match_situation` fields when SR publishes them.\n\nThe poller runs every 15 seconds against any enriched match with `sportEventUrn` set whose kickoff is in `[now-10min, now+180min]`. Responses are served straight from disk (ETag + 30-second `Cache-Control`), so expected freshness is 15s behind the live wire.\n\nReturns 404 when the poller has never seen this match — usually means either the enriched record has no SR URN (SR has not published the fixture in either team's summary cache), the match is outside the poller window, or the eventId does not exist.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "example": "1026050511",
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id of the match"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest SR match summary payload wrapped with the poller metadata",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Latest SR match summary payload wrapped with the poller metadata",
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "fetchedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the live poller last pulled this payload from SR"
                    },
                    "urn": {
                      "type": "string",
                      "description": "SR sport_event URN this summary belongs to",
                      "example": "sr:sport_event:67090592"
                    },
                    "eventId": {
                      "type": "string",
                      "description": "Kambi event id echoed from the request"
                    },
                    "sport_event": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Raw SR `sport_event` block (id, start_time, competitors, venue)"
                    },
                    "sport_event_status": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Raw SR `sport_event_status` (status, match_status, home_score, away_score, clock, period_scores)"
                    },
                    "statistics": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Raw SR `statistics.totals` with per-team possession/shots/corners/etc"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/live/{eventId}/timeline": {
      "get": {
        "summary": "Fetch the latest SportRadar timeline (event-by-event log) for an in-play match",
        "tags": [
          "live"
        ],
        "description": "Returns the ordered list of match events harvested from SportRadar: `match_started`, `period_start`, `goal_scored` (with scorers + assists), `yellow_card`, `red_card`, `substitution`, `video_assistant_referee` (VAR), `injury_time_shown`, `period_score`, `match_ended`, etc. Each entry carries an ISO `time`, a SR `type` code, and optional `commentary`, `match_clock`, `period`, and player references.\n\nRefreshed on the same 15-second live-poller cadence as `/summary`. Returns 404 with the same semantics as `/summary` — the poller has never seen this match.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "example": "1026050511",
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id of the match"
          }
        ],
        "responses": {
          "200": {
            "description": "Latest SR timeline payload wrapped with the poller metadata",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Latest SR timeline payload wrapped with the poller metadata",
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "fetchedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "urn": {
                      "type": "string",
                      "example": "sr:sport_event:67090592"
                    },
                    "eventId": {
                      "type": "string"
                    },
                    "sport_event": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "sport_event_status": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "timeline": {
                      "type": "array",
                      "description": "Ordered list of match events (goals, cards, subs, VAR, period changes)",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/live/{eventId}/lineups": {
      "get": {
        "summary": "Fetch starting lineups + formations for a match (cached once per fixture)",
        "tags": [
          "live"
        ],
        "description": "Returns the SportRadar lineups payload — starting XI, formations (e.g. `\"4-2-3-1\"`), coaches, and substitutes for both teams. Unlike `/summary` and `/timeline` this is fetched **once** on the first poll where SR has published it (typically 15-60 minutes before kickoff) and cached for the duration of the match — the data does not change during play.\n\nReturns 404 when SR has not yet published lineups, which is normal until close to kickoff. Clients should poll periodically and accept 404 gracefully.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "example": "1024044217",
            "in": "path",
            "name": "eventId",
            "required": true,
            "description": "Numeric Kambi event id of the match"
          }
        ],
        "responses": {
          "200": {
            "description": "SR lineups payload wrapped with the poller metadata",
            "content": {
              "application/json": {
                "schema": {
                  "description": "SR lineups payload wrapped with the poller metadata",
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "fetchedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "urn": {
                      "type": "string",
                      "example": "sr:sport_event:61301151"
                    },
                    "eventId": {
                      "type": "string"
                    },
                    "lineups": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Raw SR lineups: per-team starting XI, formation, coach, substitutes"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/cms/market-config": {
      "get": {
        "summary": "GET /dataengine/cms/market-config",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      },
      "post": {
        "summary": "POST /dataengine/cms/market-config",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    },
    "/team-aggregates/{teamId}": {
      "get": {
        "summary": "Per-team rich aggregates for AI insight prompts",
        "tags": [
          "aggregates"
        ],
        "description": "Returns a comprehensive season-scoped statistical profile for one team — corner, card, foul, free-kick, goal, match-state and form-context aggregates derived from every closed SR match summary in the current season. Powers Junibet AI insight prompts (\"AIK averages 6.2 corners home / 5.3 away, 7.1 vs top-half opposition\").\n\nCACHING: 6h TTL on disk under `computed/team-aggregates/{teamId}.json`. The route serves the cached payload with a 60s Cache-Control + ETag; pass `?refresh=1` to force re-compute. Standings ingest piggybacks a background recompute so the cache stays warm.\n\nNULL HANDLING: Fields that depend on timeline data (goal-by-interval, set-piece / open-play / penalty splits, late winners, comeback wins, penalties for/against) are `null` whenever the cached SR summaries lack a `timeline` array — currently the common case. The frontend / prompt layer must hide null fields rather than rendering 0.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "in": "query",
            "name": "refresh",
            "required": false,
            "description": "When `1`, force a fresh compute even if the disk cache is < 6h old."
          },
          {
            "schema": {
              "type": "string"
            },
            "example": "sr:competitor:1764",
            "in": "path",
            "name": "teamId",
            "required": true,
            "description": "SR competitor URN, e.g. sr:competitor:1764 (AIK)."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-6"
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          },
          "503": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/player-aggregates/{playerId}": {
      "get": {
        "summary": "Per-player rich aggregates for AI insight prompts",
        "tags": [
          "aggregates"
        ],
        "description": "Returns season-scoped per-player aggregates: attacking totals + per-90 metrics, efficiency ratios, discipline + suspension-watch flag, and form (goals/assists/shots in last 5 matches + scoring streak). Computed from every closed SR match summary that lists the player in `statistics.totals.competitors[].players[]`.\n\nCACHING: 6h TTL on disk under `computed/player-aggregates/{playerId}.json`. The route serves with 60s Cache-Control + ETag; `?refresh=1` forces re-compute.\n\nNULL HANDLING: Per-90 metrics depend on `minutes_played`, which SR does NOT ship in the per-player team-summary block for most leagues. When minutes are missing every per-90 field is `null`. `key_passes`, `tackles`, `interceptions` are also always `null` because SR does not surface them in this dataset.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "in": "query",
            "name": "refresh",
            "required": false,
            "description": "When `1`, force a fresh compute even if the disk cache is < 6h old."
          },
          {
            "schema": {
              "type": "string"
            },
            "example": "sr:player:869030",
            "in": "path",
            "name": "playerId",
            "required": true,
            "description": "SR player URN, e.g. sr:player:869030."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-7"
                }
              }
            }
          },
          "404": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          },
          "503": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/def-0"
                }
              }
            }
          }
        }
      }
    },
    "/": {
      "get": {
        "summary": "GET /",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Default Response"
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://api.insideformation.com/dataengine",
      "description": "Production"
    },
    {
      "url": "http://localhost:3100/dataengine",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "lobby",
      "description": "Client-facing lobby feed endpoints. One call returns everything needed to render a front page."
    },
    {
      "name": "match-detail",
      "description": "Per-match widget feeds, odds catalogues, and live summaries."
    },
    {
      "name": "league",
      "description": "League-scoped widgets: leader boards, standings, liga-bäst rankings, standalone highlights."
    },
    {
      "name": "junibet-features",
      "description": "Junibet-only feature endpoints that hybridize ValueStats confidence with Kambi betting patterns."
    },
    {
      "name": "live",
      "description": "In-play match data: live summaries, timelines, lineups (SportRadar-backed). Polled every 15 seconds while a fixture is in the window [kickoff-10min, kickoff+180min]. Responses are raw SportRadar payloads; match_status=not_started until the whistle."
    },
    {
      "name": "mappings",
      "description": "Canonical SR↔Kambi identifier bridge. Every enriched football match has its SportRadar sport_event URN persisted at ingest time via deterministic URN+kickoff matching (never fuzzy names). These endpoints let you convert between Kambi numeric event ids and SR sr:sport_event:* URNs in either direction."
    },
    {
      "name": "admin",
      "description": "Operational endpoints: pipeline triggers, cache refreshes, health, cost, alerts."
    },
    {
      "name": "discovery",
      "description": "Introspection endpoints for widgets, journalists, recipes, catalogs."
    },
    {
      "name": "content",
      "description": "AI article generation and lifecycle management."
    },
    {
      "name": "insights",
      "description": "Pre-match data-mined insights — surprising-but-statistically-significant patterns supplementing ValueStats. Patterns surface things like \"AIK has only won 1 of 20 matches against Malmö\" or \"team X with player Y starting wins 78% vs 23% without\". Mined every 6h pre-match, cached per event."
    },
    {
      "name": "catalog",
      "description": "Self-service API catalog. One call returns every endpoint, every widget, every dataset, plus auth model and refresh cadences. Designed for downstream consumers (mobile apps, web frontends, other backends) that need to discover what the engine exposes without reading the source."
    },
    {
      "name": "admin-packages",
      "description": "Per-match approval queue for clients that opt into editorial moderation (Hemmaklubben). Generated articles + videos land in a Package in `pending_approval` status; editors approve / reject / edit / unpublish through these endpoints. Only packages that reach `live` (or `scheduled` whose `scheduledFor` is in the past) become visible in the lobby feed."
    },
    {
      "name": "aggregates",
      "description": "Per-team and per-player rich aggregates derived from cached SR match summaries. Powers Junibet AI insight prompts so a bettor evaluating a market gets concrete season-level patterns (\"AIK averages 6.2 corners home / 5.3 away, 7.1 vs top-half opposition\"; \"Player X has 3 goals in last 5 with 1.4 shots-on-target per match\"). 6h on-disk TTL per-id with `?refresh=1` override; standings ingest piggybacks a background recompute."
    }
  ]
}
