Hockey API#
The Hockey API covers ice hockey (NHL and international leagues): leagues, teams with ELO ratings, matches with period-by-period scores, overtime/shootout flags, decimal odds and model-based predictions.
Base URL: https://sports.bzzoiro.com/hockey/api/v2/
Access#
Requires the Sports Addon ($5/month) — it unlocks the tennis, CS2, darts, hockey, basketball and horse racing APIs plus their MCP servers. Get it at /addons/. You also need a free account token from /register/.
Authorization: Token YOUR_API_KEY
Without a token you get 401:
{"error": "authentication required", "code": "authentication_required",
"detail": "Send Authorization: Token <YOUR_TOKEN> on every request.",
"register": "https://sports.bzzoiro.com/register/"}
With a token but no addon you get 402:
{"error": "Sports Addon required", "code": "addon_required",
"detail": "Tennis, CS:GO, darts, hockey, basketball and horse racing APIs require the Sports Addon ($5/mo).",
"pricing_url": "https://sports.bzzoiro.com/pricing/"}
List endpoints paginate with limit (default 50, max 200) and offset, and
return {count, next, previous, results}.
Endpoints#
| Endpoint | Description |
|---|---|
GET /hockey/api/v2/leagues/ |
List leagues |
GET /hockey/api/v2/leagues/{id}/ |
League detail |
GET /hockey/api/v2/teams/ |
List/search teams with ELO |
GET /hockey/api/v2/teams/{id}/ |
Team detail |
GET /hockey/api/v2/matches/ |
List matches |
GET /hockey/api/v2/matches/live/ |
Matches in play right now |
GET /hockey/api/v2/matches/{id}/ |
Match detail with period scores and odds |
GET /hockey/api/v2/matches/{id}/odds/ |
Per-bookmaker odds, every market |
GET /hockey/api/v2/matches/{id}/h2h/ |
Head-to-head record and season form |
GET /hockey/api/v2/standings/ |
League table |
GET /hockey/api/v2/predictions/ |
Model win probabilities |
GET /hockey/api/v2/predictions/{id}/ |
One prediction |
The machine-readable schema is public at /hockey/api/schema/, with an
interactive UI at /hockey/api/docs/ and a themed explorer
at /docs/explorer/hockey/.
Leagues#
Query parameters
| Param | Type | Description |
|---|---|---|
country |
string | ISO country code |
include_inactive |
bool | Include inactive leagues |
limit / offset |
int | Pagination (default 50, max 200) |
{"count": 14, "results": [
{"id": 3, "name": "NHL", "country": "USA"}
]}
Teams#
Query parameters
| Param | Type | Description |
|---|---|---|
search |
string | Name search |
country |
string | ISO country code |
limit / offset |
int | Pagination |
Responses are cached ~5 minutes.
curl -H "Authorization: Token YOUR_API_KEY" \
"https://sports.bzzoiro.com/hockey/api/v2/teams/?search=rangers"
{"count": 1, "results": [
{"id": 118, "name": "New York Rangers", "short_name": "NYR",
"country_code": "US", "elo_rating": 1671}
]}
Matches#
Query parameters
| Param | Type | Description |
|---|---|---|
league |
int | Filter by league id |
team |
int | Matches involving this team id |
status |
string | scheduled, live, finished, awarded, postponed, cancelled |
date_from / date_to |
date | YYYY-MM-DD. Without either, the list returns the next 7 days, oldest first. Any of the filters above lifts that window and returns the full archive, newest first. |
limit / offset |
int | Pagination |
curl -H "Authorization: Token YOUR_API_KEY" \
"https://sports.bzzoiro.com/hockey/api/v2/matches/?team=118&status=finished&date_from=2026-04-01"
{"count": 22, "results": [
{"id": 40312, "league": {"id": 3, "name": "NHL"},
"home_team": {"id": 118, "name": "New York Rangers", "short_name": "NYR"},
"away_team": {"id": 121, "name": "Boston Bruins", "short_name": "BOS"},
"match_date": "2026-04-12T23:00:00+00:00", "status": "finished",
"round_name": "Regular season", "home_score": 4, "away_score": 3,
"is_overtime": true, "is_shootout": false, "winner_id": 118}
]}
GET /matches/live/ returns in-play matches only (cached 30 seconds).
GET /matches/{id}/ adds period scores, the current period/minute for live
games and decimal odds:
{"id": 40390, "status": "live",
"home_score": 2, "away_score": 1,
"periods_score": "1-0, 1-1", "current_period": 3, "current_minute": 7,
"odds_home": 1.85, "odds_away": 1.95}
Note:
is_overtimeandis_shootouttell you how a finished game was decided — useful for regulation-time markets, where an overtime win counts differently.
Odds#
GET /hockey/api/v2/matches/{id}/odds/ returns every price we hold for one match: the
match-winner market flattened per bookmaker, and below it every other market the
books offered, grouped by kind, line and period.
curl -H "Authorization: Token YOUR_API_KEY" \
"https://sports.bzzoiro.com/hockey/api/v2/matches/40390/odds/"
{"match_id": 40390, "match_date": "2026-04-12T23:00:00+00:00",
"home_team_name": "New York Rangers", "away_team_name": "Boston Bruins",
"bookmakers_count": 14, "source": "multi",
"bookmakers": [
{"bookmaker": "Bet365", "bookmaker_slug": "bet365",
"odds_home": 2.54, "movement_home": "SHORTENING",
"odds_draw": 4.21, "movement_draw": "STABLE",
"odds_away": 2.40, "movement_away": "DRIFTING",
"updated_at": "2026-04-12T21:31:48Z"}
],
"markets": [
{"market_kind": "1X2", "market_family": "1X2", "market_line": null,
"market_period": "FT", "selections": ["HOME", "DRAW", "AWAY"],
"bookmakers": [
{"bookmaker": "Bet365", "bookmaker_slug": "bet365",
"prices": {"HOME": {"price": 2.54, "movement": "SHORTENING"},
"DRAW": {"price": 4.21, "movement": "STABLE"},
"AWAY": {"price": 2.40, "movement": "DRIFTING"}},
"updated_at": "2026-04-12T21:31:48Z"}
]}
]}
| Field | Meaning |
|---|---|
source |
multi per-bookmaker prices, consensus the single stored price when the match is not yet linked to a priced feed event, none when nobody has priced it |
bookmakers_count |
Bookmakers with at least one match-winner price |
movement_* |
SHORTENING, DRIFTING or STABLE against the previous price we saw; null when there is no previous price |
market_kind |
Market type: 1X2, DNB, AH, OU |
market_family |
Distinguishes markets sharing a kind (total goals vs corners vs a single side's total) |
market_line |
The handicap or total the market is quoted on; null for a plain winner market |
market_period |
FT for the full match; period codes otherwise |
Prices come from several sources and are pooled per match. A bookmaker appears once per market with its most recent price.
Standings#
GET /standings/?league={id} ranks a league by points, then points per game,
then goal difference. Overtime results stay separate from regulation ones — in
hockey they are different events, and a table that folds them together cannot be
used for regulation-time markets.
curl -H "Authorization: Token YOUR_API_KEY" \
"https://sports.bzzoiro.com/hockey/api/v2/standings/?league=3"
{"league": {"id": 3, "name": "NHL", "country": "USA"},
"standings": [
{"position": 1, "team": {"id": 118, "name": "New York Rangers", "short_name": "NYR"},
"matches_played": 82, "wins": 45, "losses": 27,
"overtime_wins": 6, "overtime_losses": 4,
"points": 100, "points_per_game": 1.22,
"goals_scored": 254, "goals_conceded": 219, "goal_difference": 35,
"form_string": "WWLOTLW", "current_streak": 2,
"calculated_at": "2026-08-19T05:38:00+00:00"}
]}
The table is built from the daily form calculation, so calculated_at tells you
how fresh a row is. league is required.
Predictions#
Query parameters
| Param | Type | Description |
|---|---|---|
upcoming |
bool | Only matches not yet started |
limit / offset |
int | Pagination |
{"count": 7, "results": [
{"id": 5107, "match_id": 40395,
"home_win_prob": 0.55, "away_win_prob": 0.45,
"predicted_winner_id": 118, "confidence": 0.55}
]}
Head-to-head and form#
GET /matches/{id}/h2h/ gives the record between the two teams and each side's
season form, split home and away. Everything is expressed in this match's terms
— home is the home team of this fixture, whichever way the record was
originally stored.
curl -H "Authorization: Token YOUR_API_KEY" \
"https://sports.bzzoiro.com/hockey/api/v2/matches/16264/h2h/"
{"match_id": 16264,
"head_to_head": {"total_matches": 5, "home_wins": 4, "away_wins": 1,
"home_goals": 15, "away_goals": 5, "avg_total_goals": 4.0,
"recent_matches": [
{"date": "2025-12-23T18:30:00+00:00", "winner": "home",
"home_goals": 4, "away_goals": 1}]},
"home_form": {"matches_played": 82, "wins": 45, "losses": 27,
"overtime_wins": 6, "overtime_losses": 4, "points": 100,
"points_per_game": 1.22, "form_string": "WWLOTLW", "current_streak": 2,
"home": {"matches": 41, "wins": 26, "points_per_game": 1.44},
"away": {"matches": 41, "wins": 19, "points_per_game": 1.0}},
"away_form": {"...": "same shape"}}
head_to_head is null when the two teams have no recorded meeting, and a
*_form is null for a team with no season record yet.
Coverage and off-season#
Hockey runs October to June. Between those months the match list is correctly empty, and an empty list on its own cannot tell you whether the season is over or the sync is broken.
GET /api/v2/coverage/?sport=hockey says so directly, without a token:
{"sport": "hockey", "status": "off_season", "events_next_7d": 0,
"events_next_30d": 0, "priced_next_7d": 0, "live_now": 0,
"next_event_at": null, "last_event_at": "2026-06-18T00:00:00+00:00"}
status is in_season (fixtures within seven days), between_events (nothing
this week, something later), off_season (nothing ahead and the last event is
more than a fortnight old) or no_fixtures. Drop the ?sport= to get every
sport in one call — worth doing before a polling loop decides what to ask for.