← API overview

{season}/schedule.json

/api/v1/{season}/schedule.json

All scheduled games for one season, including unplayed ones. Cancelled games are flagged to distinguish from null scores.

Seasons

Replace {season} with one of 2026, 2025, 2024, 2023, 2022. Not every season has every endpoint — check themanifest first. If it is not listed there, the URL 404s.

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
weeknumber|nullWeek number. 98 = semifinals, 99 = finals.
weekLabelstring|nullHuman label — "Week 3", "Semifinals", "Finals".
isPostseasonbooleanTrue for weeks 98 and 99.
datestring|nullISO date (YYYY-MM-DD).
timestring|nullLocal start time as published.
locationstring|nullVenue name.
locationAddressstring|nullVenue street address.
divisionstring|nullDivision this fixture belongs to, where applicable.
awayAbbrevstring|nullCanonical away-team abbreviation.
awayNamestring|nullFull away-team name.
awayScorenumber|nullAway goals, or null if the game has not been played.
homeAbbrevstring|nullCanonical home-team abbreviation.
homeNamestring|nullFull home-team name.
homeScorenumber|nullHome goals, or null if the game has not been played.
cancelledbooleanAlways present. True for games that will never be played.
youtubeUrlstring|nullFull-game video, where one exists.

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/2025/schedule.json
JavaScript
// Games with video, and games that were cancelled
const res = await fetch('https://pul-stats-hub.pages.dev/api/v1/2025/schedule.json');
const { data } = await res.json();

const watchable = data.filter((g) => g.youtubeUrl);
const cancelled = data.filter((g) => g.cancelled);
// A null score alone does NOT mean cancelled - check the flag.
const notYetPlayed = data.filter((g) => g.homeScore === null && !g.cancelled);

console.log(watchable.length, cancelled.length, notYetPlayed.length);
Python
# Venues used in 2025
import json, urllib.request

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

venues = {g["location"] for g in games if g["location"]}
for venue in sorted(venues):
    print(venue)

Worth knowing

  • A null score means either "not played yet" or "cancelled" — `cancelled` is the flag that tells them apart. That is why it is always present rather than only on cancelled rows.
  • Unlike games.json, this includes unplayed fixtures. That is the point of the schedule.

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