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
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
| Name | Type | Default | Description |
days | int | 8 | How far ahead to look. Clamped to 1–30. |
season | int | 2026 | Season year. Must be one of 2006–2026. Finished seasons have no upcoming games and return an empty list. |
seasontype | int | 2 | 2 regular season, 3 postseason. With 2, scheduled playoff games inside the window are included automatically (is_postseason: true). |
refresh | bool | — | 1 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
| Field | Meaning |
window | The 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_known | false when ESPN gave no home/away designation (neutral-site oddities); the two sides are then in arbitrary order. |
home.win_probability / away.win_probability | PPG model win chance, percent. The two always sum to 100. |
home.market_win_probability / away.market_win_probability | Moneyline implied probability with the vig removed (normalized to sum to 100), percent. null when no line is available. |
strength_per_game | The team's PPG total strength divided by games played — the quantity whose difference drives prediction.gap. |
games_played | Games 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_margin | Model favorite (team abbreviation, or "Even") and the raw total-strength gap between the two teams. |
prediction.gap | Home strength_per_game minus away. Positive favors home. The logistic win curve uses this. |
prediction.confidence | bucket_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. |
market | Raw 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"
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
| Name | Type | Default | Description |
abbr | path | — | Team abbreviation, case-insensitive. Uses ESPN's codes, including the odd ones: ARZ, BLT, CLV, HST, LA (Rams), WAS. Unknown → 404. |
season | int | 2026 | Season year. |
seasontype | int | 2 | 2 regular season, 3 postseason. |
refresh | bool | — | 1 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
| Field | Meaning |
off_strength / def_strength / total_strength | Season-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_scored | The 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_change | This game's contribution to the strengths above. null when no baseline exists. |
upcoming_games[].team_advantage | This team's strength minus the opponent's, per component (raw totals). |
upcoming_games[].prediction | Same shape as in /api/upcoming, but gap is from this team's perspective (positive favors this team). |
is_postseason / round_label / opponent_strength_basis | Present 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
- Errors. Bad query parameters return
400; /api/upcoming returns {"error": "..."}, /api/team returns a plain-text message. Unknown team → 404.
- Probabilities are percentages (0–100) except
prediction.win_probability_raw and confidence.accuracy, which are fractions (0–1).
- Model. Win probability is a logistic on the per-game strength gap (scale 7.0). It is calibrated against every season since 2006 — see the Backtest page for hit rates by gap band and predicted-vs-actual bins.
- Freshness. Live-season data is re-fetched from ESPN every 15 minutes (every 90 s while a game is in progress). Finished seasons are cached permanently.
- Market data is whatever single line ESPN attaches to a scheduled game. It is not stored historically, so there is no way to compare past predictions to past lines through this API.