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.