Team stats widgets

Two ways to put a team's stats on your own site, depending on how often you rebuild it. Full route reference (a bit denser) is at/docs.

Method 1 — Live widget, heavily cached

no build step
Drop this in your HTML. It fetches once and caches the result in the visitor's browser for 30 days (configurable) — most page loads never hit the API at all.
<div id="my-team-widget"></div>
<script
  src="https://pblidb.org/widgets/team-stats.js"
  data-team="red-legion"
  data-target="my-team-widget"
  data-cache-days="30"
></script>

Live example:

Method 2 — Fetch at build time

static sites, infrequent updates
If your site is static and rebuilds occasionally (a few times a week or less), skip the runtime API dependency entirely: fetch the data once during your build and bundle it as local JSON.
// Run this during your static site's build step (Node 18+, no dependencies).
// Fetch once per build, write to a local JSON file, and read that file
// from your templates — no runtime dependency on this API at all.
import { writeFileSync } from "node:fs";

const TEAM = "red-legion"; // your team's slug — see /docs or ask us for yours
const BASE = "https://pblidb.org/v1";

const [team, stats, roster] = await Promise.all([
  fetch(`${BASE}/teams/${TEAM}`).then((r) => r.json()),
  fetch(`${BASE}/teams/${TEAM}/stats`).then((r) => r.json()),
  fetch(`${BASE}/teams/${TEAM}/roster`).then((r) => r.json()),
]);

writeFileSync("src/data/team-stats.json", JSON.stringify({ team, stats, roster }, null, 2));
// Your site generator (Eleventy, Hugo, Astro, Jekyll, ...) reads that file
// like any other local data source. Re-run this script whenever you
// rebuild — nothing on the live site ever calls this API directly.

Finding a team's other division lines

multi-team search
Many clubs field several roster lines under related-but-different names — e.g. PaintballFIT is a pro line, and the same club also fields Hydra Kidz, Hydra.Fit, and others. Each is resolved as its own separate team (see /docs's note on resolveEntities) — there's no confirmed shared id tying them together, so guessing they're one team would risk merging two genuinely different clubs that happen to share a naming pattern. Instead, search by a substring of the name and pull stats for every match:
// A club often fields several roster lines under related-but-different
// names — resolveEntities() correctly does NOT guess that "Hydra Kidz" and
// "PaintballFIT" are the same club (see /docs), so find the whole family
// with a substring search first, then pull stats for each match:
const BASE = "https://pblidb.org/v1";

const { data: hydraTeams } = await fetch(`${BASE}/teams?q=hydra`).then((r) => r.json());
// -> Hydra, Hydra Kidz, Hydra Mentality, Hydra.Fit, and more

const stats = await Promise.all(
  hydraTeams.map((t) => fetch(`${BASE}/teams/${t.id}/stats`).then((r) => r.json()))
);

// Note: "hydra" won't find "PaintballFIT" or "Texas FIT" even though
// they're the same real club's other divisions — it's a plain substring
// match on the resolved name, not org-aware. Search each known naming
// pattern separately (?q=hydra, ?q=paintballfit, ?q=texas+fit, ...) and
// combine the results yourself if you need the whole club.

Try it live:

Type above to search.

Which one should I use?

Live widget

If your site rebuilds rarely (or not at all — e.g. a CMS page) but you still want stats to update within a day or so, or you don't control the build pipeline.

Build-time fetch

If you already have a build step (most static site generators do) and would rather have zero runtime dependency on this API — nothing can go down on your site if this API is ever unavailable.

Both use the same routes

GET /v1/teams/{slug},GET /v1/teams/{slug}/stats, andGET /v1/teams/{slug}/roster.

Full reference at/docs; trythis example to find a team slug to experiment with.