Retry-After (capped at 120 s), and
failures raise classed conditions: fastRhockey_no_data (the provider has
nothing for the request), fastRhockey_asset_fetch (the answer is unknown:
a 403/5xx, a block, an empty body, a body for another game) and
fastRhockey_bad_request, all inheriting fastRhockey_error. Results are
fastRhockey_data; return_parsed = FALSE returns the raw payload.
del_* (12), icehl_* (12), eihl_* (9), metal_* (8).khl_* wrappers over khl.ru and the webcaster API.shl_* wrappers, including shl_ssgt_uuid() and
shl_player_crosswalk().liiga_* wrappers.sihf_* wrappers.elh_* wrappers.ha_* wrappers.nl_* wrappers. The site's
robots.txt disallows /api; building on it was approved by the owner on
2026-10-12 for polite, incremental use only (at least 1 s between
requests, no bulk backfill or published mirror without asking again), and
the ruling is repeated in each function's documentation.magnus_* wrappers.vhl_* wrappers plus vhl_tn().espn_mch_* and espn_wch_*).game_id, National League game_id and unix times,
eSports epoch-millisecond timestamps, KHL webcaster *_ts, Czech
Extraliga sort_order) are doubles, and return_as_pandas and **kwargs
are dropped.load_pwhl_shifts() and load_pwhl_xg_pbp() — loaders for the two
published PWHL release datasets that had no R access (per-player shift
charts and xG-enriched play-by-play), completing coverage of every
hockey tag on the sportsdataverse-data releases.load_nhl_groups(), load_nhl_group_seasons(), load_nhl_group_aliases()
and load_nhl_team_group_seasons() — season-by-season conference and
division reference tables from the nhl_groups sportsdataverse-data
release: each group's name and parent as of every season, every source's
ids and names for it, and each team's conference and division by season
(1918+, keyed by the season's end year).nhl_game_shifts() gains a detailed argument: detailed = TRUE returns
the per-player shift records (one row per player-shift with game-second
on/off times) instead of the aggregated one-row-per-change frame (#26).espn_mch_* / espn_wch_* wrappers take
the sportsdataverse-py arguments the shared helpers used to drop (scoreboard
week, season_type, groups, limit; standings group,
standings_type; leaders sort; coach_record record_type; limit on
teams_site, team_roster, transactions and season_awards); espn_nhl_*
requests are unchanged. New espn_{mch,wch}_{pbp,team_box,player_box}
wrappers mirror the NHL ones. The college @return tables are now checked
against captured mch/wch bodies (30 verified; injuries, transactions and
rosters were empty in every capture and say so).Retry-After) and
HTML tables parse about 6x faster (a stats page 24.7 s -> 4.0 s).khl_player_stats(position = "goalies") returned 20 goalies who had not
played (khl.ru orders goalies by goals-against average ascending and ignores
sort fields). It now fetches every page (the count from nav.max) and
returns one list sorted by games played, then wins, then name, as
sportsdataverse-py does; page is ignored for goalies. A partial list (a
missing page, a row count that does not fit the page count, a player on two
pages, more than 30 pages) raises fastRhockey_asset_fetch. The goalie
columns (w, l, sop, ga, sv, sv_pct, gaa, so, toi) are now
documented and typed as in sdv-py: sv_pct is on a 0-100 scale (92.2),
gaa is double, toi is mm:ss text with "-" as NA. Skaters are
unchanged.
ESPN college hockey routes match sportsdataverse-py: coach_record asks
for coaches/{id}/record/{record_type} (default 0) and coach_season for
coaches/{id}/seasons/{season}. calendar deliberately keeps reading the
scoreboard payload: sportsdataverse-py's /calendar route answers 404 for
both college leagues.
ESPN scoreboards (espn_nhl_scoreboard() and the college ones): the
broadcast column joins every network name with ", " ("ESPNU, ESPN+"; feed
order, deduped), exactly as sportsdataverse-py; it used to carry ESPN's
"/"-joined first-market string. Team schedules use the same rule.
ESPN teams_site, team_schedule and standings returned empty frames on
college bodies (no alternateColor; played and unplayed games mixing score
types, which also hit NHL schedules spanning both; a standings table at the
root of the body).
Type change: espn_nhl_team_schedule() home_score / away_score are now
always character, as documented; a fully played season used to come back as
double.
ESPN college limit arguments now default as in sportsdataverse-py
(scoreboard, team_roster and transactions 500, teams_site 1000,
season_awards 200), so R and Python fetch the same page; NHL requests are
unchanged.
KHL: when khl.ru answers session priming with its ServicePipe anti-bot
challenge page, the error now says so ("khl.ru served an anti-bot challenge
page; try again later or through getOption('fastRhockey.proxy')") instead
of reporting a missing sessid.
The NHL release loaders rejected the 2009-10 season: every load_nhl_*()
release loader (and .nhl_release_loader()'s default) required
seasons >= 2011, but each sportsdataverse-data release publishes from 2010.
They now accept 2010, matching sportsdataverse-py (#765). Two of those
2009-10 files are thinner at the source: load_nhl_pbp() comes from a
lighter feed (53 columns, not 92), and load_nhl_three_stars() covers 324
of 1,318 games. load_nhl_pbp_lite()'s play_by_play_lite_<year> files
started at 2024 until the release was backfilled for 2010-2023 (2026-10-11), so
earlier lite seasons used to download nothing. load_nhl_game_rosters()
documents all twelve of its columns.
shot_distance / shot_angle (and scoring_chance) of an empty-net goal from a
team's own half were measured to the nearer net: 59 ft for a median 126-ft goal
(27 such goals in 320 PWHL games). Empty-net goals are now measured to the net
their team attacks. Every other event keeps the nearer net: per-event validation
showed own-half events with a goalie in net are near-net events whose coordinates
the feed mirrored (sdv-internal-refs hockeytech/CANVAS.md, #52).
The derived play-by-play coordinates of pwhl_pbp() and the AHL / OHL / WHL /
QMJHL *_pbp() functions put home-team events off the rink: the home flip
applied a 0-200 x 0-85 mirror to coordinates already in centre-origin feet, so
home x_coord_right ran 191 to 290 ft (PWHL game 42). The feed puts the home
team's attack toward x = 0 in every period (320 PWHL games, sdv-internal-refs
hockeytech/CANVAS.md), and the columns are now rotations of the feet frame:
x_coord_fixed / y_coord_fixed = (-x, -y), home team shooting right (the
NHL x_fixed convention); x_coord_right / y_coord_right = home (-x, -y),
visitor (x, y); x_coord_vertical / y_coord_vertical = (-y_right, x_right).
The right and vertical columns are NA when an event's side is unknown (no
team, as on faceoffs, or no home team id) instead of being treated as the
visitor's. x_coord, y_coord, shot_distance and shot_angle are
unchanged.
ahl_schedule(), ohl_schedule(), whl_schedule() and qmjhl_schedule()
returned other seasons' games. They read HockeyTech's modulekit/scorebar,
which ignores the season_id it is sent, returns games oldest first and
stops at 10,000 rows, so ahl_schedule(season = 2026) returned AHL games
from 1995 to 2012 and none from 2025-26. They now read modulekit/schedule,
which the feed scopes to one season (live 2026-10-08: AHL 2025-26 = 1,152
games, OHL 682, WHL 782, QMJHL 578), and drop any row from another season.
With neither season nor season_id they return the newest regular season.
game_status now carries the schedule view's strings (Final OT where the
scorebar said Final 1st OT), and every column is character.
The AHL, OHL, WHL and QMJHL functions that take season = (schedule,
standings, teams, rosters, leaders, ...) could resolve it to the wrong
HockeyTech season. ahl_schedule(season = 2026) resolved to the
"2026 All-Star Challenge", which the feed lists ahead of the 2025-26 regular
season, and the WHL's "2025 - 26" season names read as 2025, so
whl_schedule(season = 2026) returned 2026-27. Season names now read as
their end year the way sdv-py reads them (spaced and slashed spans, YY-ZZ,
compact YYZZ, a one-year preseason that opens the next season), one-off
events are skipped for regular-season and playoff lookups, and the newest
season counts only real regular seasons. On the 20 leagues' real seasons
captures, R and sdv-py now agree on all 485 seasons' end year and type.
nhl_teams(season =) stopped with "missing value where TRUE/FALSE needed"
(reported as "Error fetching teams") instead of returning NULL with "No
team data found" when the NHL API has no standings for the date it asks
for: the API then returns an empty list, whose nrow() is NULL. It also
now says that season is the year the season starts in (2024 for 2024-25),
which is what it queries (January of season + 1), while
most_recent_nhl_season() returns the year the season ends.
pwhl_stats() now resolves the season argument to the correct HockeyTech
season id instead of always returning the inaugural 2024 season (#50), and
the team filter works for both positions and accepts a team code
("OTT"), label ("Ottawa"), or full name — an unknown team now errors
informatively instead of silently returning the whole league (#49).
pwhl_stats(), pwhl_schedule(), and pwhl_standings() now strip the
HockeyTech JSONP envelope through the shared .hockeytech_api() helper
instead of per-function regexes that broke on payload-shape changes
(empty results for 2025 onward).
pwhl_stats() skater results are resilient to the shootout stat block
dropping out of the feed (absent after the inaugural season).
Documentation migrated to roxygen2 8.1.0; the full test suite now skips on
CRAN (everything runs in CI on every push); cph role added to
Authors@R; LICENSE year refreshed; R-hub workflow moved to the rolling
r-hub/actions@v1 tag (node24 runtime).
Added read-only Fox Sports "Bifrost" NHL wrappers (fox_nhl_*) over
api.foxsports.com/bifrost/v1/nhl/*, complementing the espn_nhl_* family:
fox_nhl_pbp() (period-based play-by-play), fox_nhl_boxscore(),
fox_nhl_odds(), fox_nhl_team_roster(), fox_nhl_team_stats(),
fox_nhl_team_gamelog(), fox_nhl_standings(), fox_nhl_league_leaders().
Flatten Fox's layout JSON into tidy fastRhockey_data tibbles. Parallels the
cfbfastR / hoopR / sportsdataverse-py fox_* families; reverse-engineering
notes + an OpenAPI 3.1 spec live in the sdv-internal-refs repo.
Added live HockeyTech wrappers for AHL, OHL, WHL, and QMJHL
(<lg>_schedule(), <lg>_pbp(), <lg>_standings(), <lg>_teams(),
<lg>_team_roster(), <lg>_player_stats(), <lg>_leaders(),
<lg>_game_summary(), <lg>_season_id(), most_recent_<lg>_season()).
Added on-ice / Corsi-Fenwick / TOI analytics across all five HockeyTech
leagues (*_game_shifts(), *_player_toi(), *_game_corsi()). Corsi/Fenwick
are proxies — the feed has no missed-shot event (corsi_includes_missed).
pwhl_pbp() now returns a superset: added shot_distance, shot_angle,
scoring_chance, on_ice_home, on_ice_away, coordinate-transform + clock
columns, and blocked_shot/hit events.
espn_nhl_*,
125 functions) covering the full ESPN hockey API surface across the site, web,
and core hosts — game data (espn_nhl_pbp, espn_nhl_team_box,
espn_nhl_player_box, espn_nhl_game_all, espn_nhl_summary,
espn_nhl_scoreboard, espn_nhl_schedule, espn_nhl_standings), athletes,
leaders, season structure, event detail, and reference catalog. Each returns a
wide, self-describing fastRhockey_data tibble with a documented column table,
built on a shared league-generic helper layer. Endpoints ESPN does not populate
for the NHL degrade gracefully to an empty frame with a warning.load_nhl_pbp_full() (parity alias of load_nhl_pbp(), which already serves
the full nhl_pbp_full release),
load_nhl_team_boxscore(), load_nhl_team_boxscores(),
load_nhl_player_boxscore(), load_nhl_player_boxscores(),
load_nhl_skater_boxscores(), load_nhl_goalie_boxscores(),
load_nhl_schedules() (NHL), and load_pwhl_team_boxscores(),
load_pwhl_player_boxscores(), load_pwhl_skater_boxscores(),
load_pwhl_goalie_boxscores(), load_pwhl_schedules() (PWHL). Each alias
forwards all arguments to its canonical counterpart.httr to httr2 behind a single internal
.retry_request() helper, and added session / environment / per-call proxy
support via options(fastRhockey.proxy = ...) (documented in ?fastRhockey).
Function outputs are unchanged.httr2 request helpers use
the native |> pipe).nhl_edge_goalie_5v5_top_10(),
nhl_edge_goalie_edge_save_pctg_top_10(),
nhl_edge_goalie_shot_location_top_10(),
nhl_edge_skater_shot_location_top_10(),
nhl_edge_skater_shot_speed_top_10(),
nhl_edge_skater_speed_top_10(),
nhl_edge_skater_zone_time_top_10(),
nhl_edge_team_shot_location_top_10(), and
nhl_edge_team_skating_speed_top_10(). They now emit a
lifecycle::deprecate_warn() and return NULL. The Edge "top-10" wrappers
that remain live (skater distance, team skating-distance, team zone-time)
are unaffected.pwhl_playoff_bracket() errored with $ operator is invalid for atomic vectors and returned NULL. The HockeyTech feed returns
team1/team2 as bare id strings (not nested objects) with wins in
sibling team1_wins/team2_wins fields; the parser now reads these
correctly.nhl_schedule(include_data_flags = TRUE) failed with
`x` and `y` must share the same src and returned a schedule with no
flag columns. load_nhl_games() returns a data.table, so the flag
left-join was being routed through the dtplyr backend, and the data.table
[, <character>] subset evaluated to the literal column names rather than
selecting columns. The index is now coerced to a tibble before the join, so
the 16 data-availability flag columns attach correctly.terminate_on to httr::RETRY
so permanent client errors (400/401/403/404/410/422) are not retried — the
functions still return NULL gracefully, but without the wasteful retry
loop and log noise. Transient 5xx/timeouts are still retried.test-load_nhl.R and test-calculate_xg.R no
longer skip_on_ci(), so the NHL loaders and the xG pipeline are exercised
on CI (xgboost is installed explicitly in the workflow). Loader tests now
request seasons that are actually published per table and assert on the keys
each release carries.col_name | types | description table, matching the convention used in
hoopR, wehoop, and cfbfastR. Functions that return a named list of
data frames get one table per frame. Column names and types were captured
from live API responses, so the tables reflect the actual returned schema.nhl_schedule() gains a game_type parameter accepting "both" (default),
"regular", or "playoffs". In season mode the function now returns
regular-season and playoff games in a single tibble. Three new columns
(series_letter, playoff_round, series_game_number) are appended to
the output; they are NA on regular-season rows.nhl_schedule(season = ...) previously returned only
what the club-schedule-season endpoint provided, which mixed preseason
(PR), regular (R), and playoff (P) rows. With this release:
game_type defaults to "both", so playoff games are now included by
default. Pass game_type = "regular" to restore regular-only behavior.PR) rows are no longer returned by season-mode calls
regardless of game_type. Use nhl_schedule(day = ...) to retrieve
preseason game schedules.nhl_playoff_schedule() is unchanged in its public API; internally it now
delegates its HTTP call to a new shared internal helper
.fetch_playoff_series().nhl_schedule() previously returned the same home_team_name,
away_team_name, home_score, away_score, and venue value in every
row regardless of the actual game. Root cause was an ifelse(scalar, vec, NA) guard in .parse_schedule_games() and .parse_club_schedule_games()
that collapsed each vector to length 1, which tibble then recycled
across all rows. Replaced with if/else, and added regression tests
asserting per-row distinct values. home_team_abbr, away_team_abbr,
and the other directly-assigned columns were not affected.nhl_player_info() used the same ifelse(scalar, vec, NA)
idiom across all 18 of its nullable fields. The function returns a 1-row
tibble per player so the bug was masked today, but the latent shape was
identical to the nhl_schedule() bug above. Replaced with if/else
for consistency and to remove the foot-gun before it surfaces (e.g., if
the function is ever refactored to handle multiple players).pwhl_schedule() gains a game_type parameter accepting "both"
(default), "regular", "playoffs", or "preseason". A new game_type
column is appended labeling each row.game_type now defaults to "both", so a call such
as pwhl_schedule(season = 2024) returns regular-season and playoff
games in a single, chronologically ordered data frame. Pass
game_type = "regular" to restore the previous regular-only behavior.
Preseason is excluded from "both"; request it with
game_type = "preseason".game_type does not yet exist for a season (e.g. playoffs
for an in-progress season), it is skipped rather than erroring, so "both"
still returns the regular-season games that do exist."date_with_day" display string.pwhl_schedule()'s winner column is now derived from
game_status and an integer comparison of the goal counts. Previously the
character goal counts were compared with >, so a double-digit score
sorted lexically ("10" < "9") and could name the wrong winner; games that
have not been played now yield winner = NA (the prior code emitted a "-"
placeholder). PWHL games cannot end in a regulation tie, so the former
"Tie" branch is gone.pwhl_player_game_log() and pwhl_streaks() gain the same game_type
parameter ("both" default, plus "regular", "playoffs", "preseason")
and a game_type column labeling each row. With "both", a player's
regular-season and playoff data are returned together. A game type the
player/season has no data in (e.g. playoffs for a non-qualifying team) is
skipped rather than erroring, so the call still returns the data that does
exist.pwhl_transactions() is intentionally unchanged: transactions are not
scoped to a game type (the playoff season feed carries none), so a combined
view would add no information.Three new season-level NHL loaders that bring NHL coverage in line with
PWHL. Each is backed by a new release tag on
sportsdataverse/sportsdataverse-data
and powered by new extractors in fastRhockey-nhl-raw/R/scrape_nhl_raw.R
and fastRhockey-nhl-data/R/nhl_data_creation.R.
| function | release tag | rows per game |
|--------------------------------|-------------------------|------------------------------|
| load_nhl_officials() | nhl_officials | one per official (refs+lins) |
| load_nhl_shots_by_period() | nhl_shots_by_period | one per team per period |
| load_nhl_shootout() | nhl_shootout | one per shootout attempt |
nhl_schedule() gained an include_data_flags = FALSE parameter. When
TRUE, the live schedule is left-joined against the cached
nhl_games_in_data_repo index from the data repo and gains 16 logical
columns (PBP, team_box, player_box, skater_box, goalie_box,
game_info, game_rosters, scoring, penalties, scratches,
linescore, three_stars, shifts, officials, shots_by_period,
shootout) telling you which pre-compiled datasets cover each game.
The schedule files in fastRhockey-nhl-raw/nhl/schedules/ and the
master schedule on the data repo carry these same flags.
scrape_nhl_raw.R now writes four additional top-level keys into both
nhl/json/raw/{game_id}.json and nhl/json/final/{game_id}.json:
officials — list of {role, name} (referees first, linesmen second)shots_by_period — long-form per-team-per-period shot totalsshootout — per-attempt summary (NULL if game didn't reach SO)plays_by_period — OLD-format index of play_indices per periodon_ice / on_ice_plus / penalty_box (final-state reconstruction,
derived from the processed PBP) — populated only in the final/
variant since they need fastRhockey-enriched on-ice columnsAll NHL season-level loaders and the shared .nhl_release_loader()
helper now live in R/nhl_loaders.R, mirroring the PWHL convention
(R/pwhl_loaders.R). The empty R/nhl_pbp.R file was removed since
it never contained an actual nhl_pbp() scraper — only loaders.
Public signatures and return types are unchanged.
Wraps the NHL Edge advanced-metrics endpoints under
https://api-web.nhle.com/v1/edge/.... Every wrapper accepts an optional
season argument (4-digit end year, e.g., 2025) — when omitted, the
/now form is used to fetch the current season. All wrappers share an
internal .nhl_edge_api() helper in R/helpers_nhl_edge.R.
| family | functions |
|---------|-----------|
| Skater | nhl_edge_skater_detail(), nhl_edge_skater_landing(), nhl_edge_skater_comparison(), nhl_edge_skater_shot_location_detail(), nhl_edge_skater_shot_location_top_10(), nhl_edge_skater_shot_speed_detail(), nhl_edge_skater_shot_speed_top_10(), nhl_edge_skater_skating_speed_detail(), nhl_edge_skater_speed_top_10(), nhl_edge_skater_skating_distance_detail(), nhl_edge_skater_distance_top_10(), nhl_edge_skater_zone_time(), nhl_edge_skater_zone_time_top_10(), nhl_cat_edge_skater_detail() |
| Goalie | nhl_edge_goalie_detail(), nhl_edge_goalie_landing(), nhl_edge_goalie_comparison(), nhl_edge_goalie_5v5_detail(), nhl_edge_goalie_5v5_top_10(), nhl_edge_goalie_save_percentage_detail(), nhl_edge_goalie_edge_save_pctg_top_10(), nhl_edge_goalie_shot_location_detail(), nhl_edge_goalie_shot_location_top_10(), nhl_cat_edge_goalie_detail() |
| Team | nhl_edge_team_detail(), nhl_edge_team_landing(), nhl_edge_team_shot_location_detail(), nhl_edge_team_shot_location_top_10(), nhl_edge_team_shot_speed_detail(), nhl_edge_team_skating_speed_detail(), nhl_edge_team_skating_speed_top_10(), nhl_edge_team_skating_distance_detail(), nhl_edge_team_skating_distance_top_10(), nhl_edge_team_zone_time_details(), nhl_edge_team_zone_time_top_10() |
First-time integration with https://records.nhl.com/site/api/. All
wrappers share an internal .nhl_records_api() helper in
R/helpers_nhl_records.R that supports cayenneExp filters,
limit/start pagination, and the records-API response shape
({data, total}).
nhl_records_franchise(), nhl_records_franchise_detail(),
nhl_records_franchise_totals(), nhl_records_franchise_team_totals(),
nhl_records_franchise_season_results(),
nhl_records_franchise_playoff_appearances()nhl_records_player(), nhl_records_player_byteam(),
nhl_records_player_stats(),
nhl_records_skater_real_time_stats_season(),
nhl_records_skater_real_time_stats_career()nhl_records_goalie_career_stats(),
nhl_records_goalie_season_stats(), nhl_records_goalie_shutout_streak()nhl_records_draft(), nhl_records_draft_lottery_odds(),
nhl_records_draft_lottery_picks(), nhl_records_draft_prospect()nhl_records_trophy(), nhl_records_award_details(),
nhl_records_hof_players()nhl_records_officials(), nhl_records_attendance(),
nhl_records_venue(), nhl_records_combine()Promotes endpoints that were previously only reachable via
nhl_stats_misc()'s generic dispatcher into discoverable, dedicated
wrappers. Documentation for nhl_stats_misc() was also updated to
enumerate every valid endpoint value.
nhl_stats_franchise(), nhl_stats_players(), nhl_stats_glossary(),
nhl_stats_country(), nhl_stats_config(), nhl_stats_ping()nhl_stats_skater_leaders(), nhl_stats_goalie_leaders(),
nhl_stats_skater_milestones(), nhl_stats_goalie_milestones()nhl_stats_team_listing(), nhl_stats_game_listing(),
nhl_stats_content_module()nhl_wsc_pbp(game_id) -- narrative-format play-by-play from
wsc/play-by-play/{gameId} (distinct from nhl_game_pbp() which uses
gamecenter/{id}/play-by-play).nhl_draft_tracker() -- live draft tracker
(draft-tracker/picks/now), distinct from nhl_draft() which hits the
static draft/picks/now endpoint.nhl_ppt_replay(game_id, event_number) -- event-level replay metadata.nhl_ppt_replay_goal(game_id, event_number) -- goal-specific replay
metadata.nhl_postal_lookup(postal_code) -- broadcast region lookup by postal
code.nhl_smartlinks(handle = NULL) -- NHL.com smart-link router.nhl_scoreboard(date = NULL) -- now accepts an optional date
argument so callers can fetch historical scoreboards (was hardcoded to
/now).nhl_meta(game_id = NULL, year = NULL, series_letter = NULL) --
added a third branch for meta/playoff-series/{year}/{seriesLetter}.nhl_draft_year(year, round = NULL) -- when round is NULL or
"all", the function now hits the /draft/picks/{year}/all shortcut
in a single request instead of looping per round.Convenience wrappers that orchestrate multiple endpoint calls into one tidy data frame:
nhl_game_ids_by_season(season, game_types, team_abbr, sleep_rate) --
iterates every team's season schedule and returns the deduplicated set
of game IDs.nhl_all_players_by_season(season, sleep_rate) -- iterates every
team's roster and flattens forwards/defensemen/goalies.nhl_player_career_stats(player_id) -- combines player/{id}/landing
with the season-by-season seasonTotals payload into a career frame.nhl_team_summary_range(start_season, end_season) -- multi-season
team summary loop over nhl_stats_teams(report_type = "summary").nhl_skater_summary_range(start_season, end_season) -- same for
skater summary.nhl_goalie_summary_range(start_season, end_season) -- same for
goalie summary.data-raw/nhl_missing_endpoint_function_mapping.md -- table of every
documented NHL API endpoint and its proposed/implemented
fastRhockey wrapper, sourced from
dfleis/nhl-api-docs,
RentoSaijo/nhlscraper,
and coreyjs/nhl-api-py.data-raw/nhl_api_web_openapi.{json,yaml} -- OpenAPI 3.0.3 spec for
api-web.nhle.com/v1/ (132 endpoints).data-raw/nhl_stats_rest_openapi.{json,yaml} -- OpenAPI 3.0.3 spec
for api.nhle.com/stats/rest/{lang}/ (21 endpoints).data-raw/nhl_records_openapi.{json,yaml} -- OpenAPI 3.0.3 spec for
records.nhl.com/site/api/ (442 endpoints).data-raw/_gen_openapi.py -- regenerator script.nhl_stats_players() now detects data: [] empty-list responses
(the API returns this when no cayenne_exp filter is supplied) and
returns NULL with a friendly hint instead of crashing
clean_names().nhl_stats_skater_leaders() and nhl_stats_goalie_leaders() no
longer pass start/limit query parameters, which the
leaders/{skaters,goalies}/{attribute} endpoint rejects with a 500
Cayenne SQL error. Roxygen now documents the valid attribute
values: skaters accept assists/goals/points; goalies accept
savePctg/gaa/shutouts.nhl_records_franchise(franchise_id = ...) now translates the
franchise_id argument into a cayenneExp=id={id} filter. The
records API does not support a path-suffix franchise/{id} form
(returns 404).nhl_records_award_details() -- replaced the broken franchise_id
argument with season_id (the award-details endpoint accepts
seasonId filtering but rejects franchiseId).nhl_stats_content_module() now guards against unnamed CMS
responses that previously crashed clean_names().Ten new season-level loaders that pull pre-compiled NHL datasets from new
release tags on
sportsdataverse/sportsdataverse-data.
All accept a seasons vector (Min: 2011) and return a fastRhockey_data
data frame, mirroring the existing load_nhl_*() API:
| function | release tag | rows per game |
|----------------------------|------------------------|--------------------------|
| load_nhl_skater_box() | nhl_skater_boxscores | one per skater |
| load_nhl_goalie_box() | nhl_goalie_boxscores | one per goalie |
| load_nhl_game_rosters() | nhl_game_rosters | one per dressed player |
| load_nhl_game_info() | nhl_game_info | one |
| load_nhl_scoring() | nhl_scoring | one per goal |
| load_nhl_penalties() | nhl_penalties | one per penalty |
| load_nhl_three_stars() | nhl_three_stars | up to three |
| load_nhl_scratches() | nhl_scratches | one per scratched player |
| load_nhl_linescore() | nhl_linescore | one |
| load_nhl_shifts() | nhl_shifts | one per shift |
The six pre-existing loaders (load_nhl_pbp(), load_nhl_pbp_lite(),
load_nhl_player_box(), load_nhl_team_box(), load_nhl_schedule(),
load_nhl_rosters()) were refactored to share a single internal worker
(.nhl_release_loader()) without changing their public signatures or return
types.
Eleven new season-level loaders that pull pre-compiled PWHL datasets from
new release tags on
sportsdataverse/sportsdataverse-data.
All accept a seasons vector (Min: 2024) and return a fastRhockey_data
data frame, mirroring the existing load_pwhl_*() API:
| function | release tag | rows per game |
|--------------------------------|----------------------------|----------------------|
| load_pwhl_skater_box() | pwhl_skater_boxscores | one per skater |
| load_pwhl_goalie_box() | pwhl_goalie_boxscores | one per goalie |
| load_pwhl_team_box() | pwhl_team_boxscores | two (home/away) |
| load_pwhl_game_info() | pwhl_game_info | one |
| load_pwhl_scoring_summary() | pwhl_scoring_summary | one per goal |
| load_pwhl_penalty_summary() | pwhl_penalty_summary | one per penalty |
| load_pwhl_three_stars() | pwhl_three_stars | up to three |
| load_pwhl_officials() | pwhl_officials | one per official |
| load_pwhl_shots_by_period() | pwhl_shots_by_period | one per period |
| load_pwhl_shootout() | pwhl_shootout | one per attempt |
| load_pwhl_game_rosters() | pwhl_game_rosters | one per dressed player|
The four pre-existing loaders (load_pwhl_pbp(), load_pwhl_player_box(),
load_pwhl_schedule(), load_pwhl_rosters()) were refactored to share a
single internal worker (.pwhl_release_loader()) without changing their
public signatures or return types.
lifecycle. The Premier Hockey
Federation ceased operations; use PWHL functions instead. Functions will be
removed in a future release._v2 variants, since the original API endpoints were
deprecated by the NHL. This means nhl_game_feed(), nhl_game_boxscore(),
nhl_schedule(), nhl_teams(), nhl_teams_roster(), and
nhl_player_info() now use the new api-web.nhle.com endpoints directly.pwhl_pbp() -- PWHL play-by-play data.pwhl_player_box() -- PWHL player box scores (skaters and goalies).pwhl_game_info() -- PWHL game information and metadata.pwhl_game_summary() -- Detailed game summary (scoring, penalties, shots
by period, three stars).pwhl_standings() -- PWHL league standings.pwhl_stats() -- PWHL stat leaders (skaters and goalies).pwhl_leaders() -- League leaders (top scorers and top goalies).pwhl_season_id() -- PWHL season ID lookup (now API-driven with fallback).pwhl_player_info() -- Player biographical and profile information.pwhl_player_game_log() -- Per-game statistics for a player in a season.pwhl_player_stats() -- Career and season-by-season statistics for a player.pwhl_player_search() -- Search for players by name.pwhl_transactions() -- Player transactions for a season.pwhl_streaks() -- Player streak data for a season.pwhl_playoff_bracket() -- Playoff bracket / series data.pwhl_scorebar() -- Recent and upcoming game scores.most_recent_pwhl_season() -- Utility to get the current PWHL season year.load_pwhl_pbp() -- Load pre-scraped PWHL play-by-play data.load_pwhl_player_box() -- Load pre-scraped PWHL player box scores.load_pwhl_schedule() -- Load pre-scraped PWHL schedules.load_pwhl_rosters() -- Load pre-scraped PWHL team rosters.update_pwhl_db() -- Update or create a PWHL play-by-play database.nhl_where_to_watch() -- streaming/broadcast availability.nhl_partner_game_odds() -- partner game odds by country.api-web.nhle.com: nhl_game_feed(),
nhl_game_boxscore(), nhl_schedule(), nhl_teams(),
nhl_teams_roster(), nhl_player_info(), nhl_player_game_log(),
nhl_stats_goalies(), nhl_stats_skaters(), nhl_stats_teams(),
nhl_stats_misc(), nhl_gamecenter_landing(),
nhl_gamecenter_right_rail(), nhl_scoreboard(), nhl_scores(),
nhl_seasons(), nhl_tv_schedule(), nhl_game_story(),
nhl_game_content(), nhl_game_shifts(), nhl_playoff_bracket(),
nhl_playoff_schedule(), nhl_playoff_carousel().helper_nhl_calculate_xg().nhl_draft_year() -- NHL API removed the /v1/draft/picks/{year}
endpoint. The function now iterates over rounds 1-7 using
/v1/draft/picks/{year}/{round}.pwhl_stats() -- resolved "object 'players' not found" error when
API calls failed. Fixed team ID resolution for skater stats.pwhl_schedule() -- the season column was inadvertently dropped
from the output. It is now included.refresh_xg_models() -- resolved "cannot change value of locked
binding" error by storing xG models in a package environment (.xg_env)
instead of top-level bindings.import(tidyverse) which violated CRAN policy.
Individual packages (dplyr, tidyr, etc.) are already imported.phf_schedule(), phf_standings(), phf_pbp(),
phf_player_box(), phf_team_box(), phf_team_roster(),
phf_team_stats(), phf_player_stats(), phf_leaders(),
phf_league_info(), phf_game_all(), phf_game_raw(),
phf_game_details(), phf_game_summary().load_phf_pbp(), load_phf_team_box(),
load_phf_player_box(), load_phf_schedule(), load_phf_rosters(),
update_phf_db().most_recent_phf_season().pwhl_season_id() now retrieves season data dynamically from the HockeyTech
API instead of using a hardcoded lookup table. Falls back to hardcoded data
when the API is unavailable..pwhl_api(), .pwhl_modulekit_url(),
.pwhl_gc_url(), and .pwhl_resolve_season_id() to reduce JSONP parsing
boilerplate across PWHL functions.lifecycle package for formal deprecation management.testthat dependency to >= 3.0.0.RUN_NHL_TESTS,
RUN_PHF_TESTS, RUN_PWHL_TESTS) via tests/testthat/helper-skip.R.helper-skip.R names.nhl_where_to_watch() returns NULL gracefully when the NHL
/v1/where-to-watch endpoint is unavailable._pkgdown.yml with reorganized reference sections and deprecated
function categories.CONTRIBUTING.md with naming conventions, testing environment
variables, conventional commits guide, and deprecation process.CLAUDE.md and .github/copilot-instructions.md to reflect
v1.0.0 changes and new PWHL endpoints.data-raw/pr_devel.md development scratchpad.cran_comments.md for CRAN submission._pkgdown.yml with reorganized PWHL reference sections.pwhl_schedule() function added.pwhl_team_roster() function added.pwhl_teams() function added.nhl_game_pbp() function added to match hockeyR.phf_league_info() function for team name inconsistency.load_phf_rosters() function added.load_nhl_rosters() function added.phf_team_logos dataset to package for reference.helper_phf_pbp_data() penalty code.try() to function examples.espn_nhl_teams() function added.load_phf_pbp() function added.load_phf_team_box() function added.load_phf_player_box() function added.load_phf_schedule() function added.update_phf_db() function added.phf_leaders() function added.phf_standings() function added.phf_player_stats() function added.phf_team_stats() function added.phf_team_roster() function added.load_phf_game() --> phf_game_all()load_phf_pbp() --> phf_pbp()load_phf_boxscore() --> phf_team_box()load_phf_raw_data() --> phf_game_raw()powerplay to fastRhockey.NEWS.md file to track changes to the package.