← API overview

games.json

/api/v1/games.json

Every completed game across every season, with final scores. One request, no pagination.

Response

Every endpoint returns the same envelope. Your data is indata — data is the array itself — there is no wrapper object inside it.

{
  "schemaVersion": "1.0",
  "generatedAt": "2026-06-23T14:03:13.152995-04:00",
  "league": "PUL",
  "data": [ ... ]
}

generatedAt is a nullable ISO-8601 timestamp that may or may not carry a UTC offset — parse leniently. It is null for 2022 and 2023, which predate the pipeline that records it.

Fields

FieldTypeMeaning
seasonstringSeason year, e.g. "2025".
weeknumberRegular-season week number. 98 = semifinals, 99 = finals.
weekLabelstring|nullHuman label — "Week 3", "Semifinals", "Finals".
isPostseasonbooleanTrue for semifinals and finals. Filter on this, not on the week number.
datestring|nullISO date (YYYY-MM-DD). null where the date is unknown.
awayAbbrevstringCanonical away-team abbreviation — join key to teams.json.
awayNamestringFull away-team name.
awayScorenumberAway goals. Final — only completed games appear here.
homeAbbrevstringCanonical home-team abbreviation.
homeNamestringFull home-team name.
homeScorenumberHome goals.

Dotted names are nested — colors.primary means{ colors: { primary } }. When the parent is a list of objects (like seasons on the manifest),seasons.season means each entry in that array has its ownseason field — access it as seasons[i].season, not seasons.season.

Examples

curl
curl -s https://pul-stats-hub.pages.dev/api/v1/games.json | head -c 400
JavaScript
// Every game that has ever finished 20-10
const res = await fetch('https://pul-stats-hub.pages.dev/api/v1/games.json');
const { data } = await res.json();

const matches = data.filter((g) => {
  const high = Math.max(g.homeScore, g.awayScore);
  const low = Math.min(g.homeScore, g.awayScore);
  return high === 20 && low === 10;
});

console.log(matches.length);
Python
# How many distinct final scorelines exist? (Scorigami)
import json, urllib.request

URL = "https://pul-stats-hub.pages.dev/api/v1/games.json"
with urllib.request.urlopen(URL) as response:
    games = json.load(response)["data"]

scorelines = {
    (max(g["homeScore"], g["awayScore"]), min(g["homeScore"], g["awayScore"]))
    for g in games
}
print(len(scorelines), "distinct scorelines in", len(games), "games")

Worth knowing

  • Completed games only. For unplayed or cancelled games use the per-season schedule endpoint.
  • Scores come from the schedule, which is authoritative over the stats pipeline — the two can disagree and the schedule wins.
  • Values change retroactively when we correct data. Check `generatedAt` if freshness matters.

Help us grow the archive!We're looking for volunteers to collect stats from historical game footage.