← API overview

{season}/teams.json

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

Team statistical totals for one season. Absent for seasons without game stats.

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
abbrevstring|nullCanonical team abbreviation.
namestring|nullFull team name.
goalsnumber|nullGoals scored.
breakGoalsnumber|nullGoals scored while on defence.
blocksnumber|nullDefensive blocks.
holdsnumber|nullOffensive points converted.
cleanHoldsnumber|nullHolds with no turnover.
passAttemptsnumber|nullPasses thrown.
turnoversnumber|nullPossessions lost.
completedPassesnumber|nullPasses completed.
completionRatenumber|nullCompletion percentage on a 0–100 scale, e.g. 89.5 — not a 0–1 fraction.
hucksnumber|nullLong throws attempted.

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/teams.json
JavaScript
// Most accurate offence of 2025 (completionRate is null for every 2023 team)
const res = await fetch('https://pul-stats-hub.pages.dev/api/v1/2025/teams.json');
const { data } = await res.json();

const rated = data.filter((t) => t.completionRate !== null);
const best = rated.sort((a, b) => b.completionRate - a.completionRate)[0];
console.log(`${best?.name}: ${best?.completionRate}% completion`); // 0-100 scale
Python
# Blocks leaders, 2025
import json, urllib.request

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

for team in sorted(teams, key=lambda t: t["blocks"], reverse=True)[:5]:
    print(team["abbrev"], team["blocks"])

Worth knowing

  • Absent for 2022 — that season has no game-level stats, so the endpoint 404s rather than returning zeros. Check the manifest before requesting it.
  • Totals cover the regular season. Postseason stats are tracked separately and are not folded in here.
  • 2023 predates full Statto exports: `breakGoals`, `holds`, `cleanHolds`, `passAttempts`, `completedPasses`, `completionRate`, and `hucks` are null on every 2023 row. `goals`, `blocks`, and `turnovers` are populated for 2023.

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