API

Two read-only JSON endpoints expose the same numbers the site renders. No authentication, no rate limit beyond the upstream ESPN cache (data refreshes at most every 15 minutes, or every 90 seconds while a game is live). All timestamps are UTC ISO 8601. Base URL: http://ppg.kiuh.com/

GET /api/upcoming GET /api/team/<abbr> Shared conventions

GET /api/upcoming Try it

Every unplayed game kicking off within the next days days — one entry per matchup — with the PPG model's win probability for each side and the market's de-vigged implied probability next to it. This is exactly what the Upcoming page renders.

Query parameters
NameTypeDefaultDescription
daysint8How far ahead to look. Clamped to 1–30.
seasonint2026Season year. Must be one of 2006–2026. Finished seasons have no upcoming games and return an empty list.
seasontypeint22 regular season, 3 postseason. With 2, scheduled playoff games inside the window are included automatically (is_postseason: true).
refreshbool1 forces a fresh ESPN fetch for the live season. Ignored for finished seasons.
Response
{
  "season": 2026,
  "season_type": 2,
  "days": 8,
  "window": { "start": "2026-09-11T15:00:00+00:00", "end": "2026-09-17T20:00:00+00:00" },
  "generated_at": "2026-09-11T20:00:00+00:00",
  "count": 15,
  "games": [
    {
      "game_id": "401872925",
      "week": 1,
      "start": "2026-09-13T17:00:00+00:00",
      "status": "STATUS_SCHEDULED",
      "in_progress": false,
      "is_postseason": false,
      "round_label": null,
      "home_known": true,
      "home": {
        "team": "CIN",
        "name": "Cincinnati Bengals",
        "win_probability": 50.0,
        "market_win_probability": 63.7,
        "strength_per_game": 0.0,
        "games_played": 0
      },
      "away": {
        "team": "TB",
        "name": "Tampa Bay Buccaneers",
        "win_probability": 50.0,
        "market_win_probability": 36.3,
        "strength_per_game": 0.0,
        "games_played": 0
      },
      "favorite": "Even",
      "favorite_margin": 0.0,
      "prediction": {
        "win_probability": 50.0,
        "win_probability_raw": 0.5,
        "gap": 0.0,
        "confidence": {
          "word": "Low",
          "accuracy": 0.529,
          "historical_accuracy": 52.9,
          "bucket_label": "0 – 2.9"
        }
      },
      "market": {
        "home_moneyline": -198,
        "away_moneyline": 164,
        "spread": -3.5,
        "over_under": 50.5,
        "details": "CIN -3.5"
      }
    }
  ]
}
Field notes
FieldMeaning
windowThe kickoff range that was searched. start is a few hours in the past so a game that has just kicked off (but isn't final) is still listed, with in_progress: true.
home_knownfalse when ESPN gave no home/away designation (neutral-site oddities); the two sides are then in arbitrary order.
home.win_probability / away.win_probabilityPPG model win chance, percent. The two always sum to 100.
home.market_win_probability / away.market_win_probabilityMoneyline implied probability with the vig removed (normalized to sum to 100), percent. null when no line is available.
strength_per_gameThe team's PPG total strength divided by games played — the quantity whose difference drives prediction.gap.
games_playedGames that contributed to the strength. Expect 0 and a 50/50 prediction in Week 1: the model needs each opponent to have another scored game before it can build a baseline.
favorite / favorite_marginModel favorite (team abbreviation, or "Even") and the raw total-strength gap between the two teams.
prediction.gapHome strength_per_game minus away. Positive favors home. The logistic win curve uses this.
prediction.confidencebucket_label is the |gap| band; accuracy / historical_accuracy are how often the model's favorite has actually won in that band across all backtested seasons (fraction / percent); word buckets that into Low / Medium / High.
marketRaw line as carried by ESPN. American moneylines; spread is from the home team's perspective (negative = home favored). null when no line is available — every completed game, and occasionally a far-future one.
Example
curl "http://ppg.kiuh.com/api/upcoming?days=3"

GET /api/team/<abbr> Try it (KC)

One team's season: its strength components, every played game scored against the opponent's averages, and every remaining game with a prediction. This feeds the team modal on the Rankings page.

Path & query parameters
NameTypeDefaultDescription
abbrpathTeam abbreviation, case-insensitive. Uses ESPN's codes, including the odd ones: ARZ, BLT, CLV, HST, LA (Rams), WAS. Unknown → 404.
seasonint2026Season year.
seasontypeint22 regular season, 3 postseason.
refreshbool1 forces a fresh ESPN fetch.
Response
{
  "team": "KC",
  "season": 2026,
  "season_type": 2,
  "start_week": 1,
  "retroactive": true,
  "off_strength": 12.5,
  "def_strength": 8.0,
  "total_strength": 20.5,
  "played_games": [
    {
      "week": 1,
      "opponent": "DEN",
      "points_scored": 27,
      "points_allowed": 20,
      "opp_avg_allowed": 22.4,
      "opp_avg_scored": 19.1,
      "off_change": 4.6,
      "def_change": -0.9
    }
  ],
  "upcoming_games": [
    {
      "week": 2,
      "opponent": "LV",
      "opponent_strength": { "off": -3.1, "def": 1.2, "total": -1.9 },
      "team_advantage":    { "off": 15.6, "def": 6.8, "total": 22.4 },
      "favorite": "KC",
      "favorite_margin": 22.4,
      "prediction": {
        "win_probability": 81.3,
        "win_probability_raw": 0.813,
        "gap": 10.3,
        "confidence": { "word": "Medium", "accuracy": 0.742, "historical_accuracy": 74.2, "bucket_label": "6.2 – 10.7" }
      }
    },
    {
      "week": 1,
      "opponent": "BUF",
      "is_postseason": true,
      "round_label": "Wild Card",
      "opponent_strength_basis": "regular_season",
      "...": "same fields as above"
    }
  ]
}
Field notes
FieldMeaning
off_strength / def_strength / total_strengthSeason-to-date sums: Σ (points scored − opponent's avg allowed) and Σ (opponent's avg scored − points allowed). Raw totals, not per-game.
played_games[].opp_avg_allowed / opp_avg_scoredThe opponent's full-season averages excluding this week (retroactive baseline). null if the opponent has no other scored game yet.
played_games[].off_change / def_changeThis game's contribution to the strengths above. null when no baseline exists.
upcoming_games[].team_advantageThis team's strength minus the opponent's, per component (raw totals).
upcoming_games[].predictionSame shape as in /api/upcoming, but gap is from this team's perspective (positive favors this team).
is_postseason / round_label / opponent_strength_basisPresent only on scheduled playoff games surfaced while viewing the regular season. Playoff matchups are scored from regular-season strengths — a 0–3 game postseason sample is too thin to use.
Example
curl "http://ppg.kiuh.com/api/team/phi?season=2026"

Shared conventions