{"id":2892,"date":"2026-04-28T10:00:00","date_gmt":"2026-04-28T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=2892"},"modified":"2026-04-18T14:32:20","modified_gmt":"2026-04-18T14:32:20","slug":"nhl-odds-api-puck-lines-player-props","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/","title":{"rendered":"NHL Odds API: Puck Lines, Totals &#038; Player Props (Python + Free Tier)"},"content":{"rendered":"<h2>NHL Odds API: The Problem<\/h2>\n<p>The NHL doesn&#8217;t run a public odds API. DraftKings, FanDuel, BetMGM, and Caesars all carry NHL puck lines, totals, and player props \u2014 but none of them expose a developer-facing endpoint. Pinnacle has sharp NHL lines but shut down public API access for US retail. If you want to build anything around NHL odds \u2014 a line-shopping tool, an arb scanner, a player-prop model, a Closing Line Value tracker \u2014 your options have historically been:<\/p>\n<ul>\n<li><strong>Scrape sportsbook websites<\/strong> \u2014 fragile, rate-limited, and increasingly blocked by Cloudflare.<\/li>\n<li><strong>Pay enterprise feeds<\/strong> \u2014 Sportradar and Betgenius quote $3,000+\/month for real-time hockey.<\/li>\n<li><strong>Use a generic odds API<\/strong> \u2014 most cap out at 20\u201340 books and paywall player props and historical data.<\/li>\n<\/ul>\n<p>There&#8217;s a third option. OddsPapi aggregates NHL odds from <strong>350+ bookmakers<\/strong> \u2014 including Pinnacle, Bet365, DraftKings, FanDuel, BetMGM, Caesars, BetRivers, and the full Betfair Exchange \u2014 on a free developer tier. This tutorial shows you exactly how to pull live moneylines, puck lines, totals, and player props in Python, with real API responses from a verified NHL game.<\/p>\n<h2>Old Way vs OddsPapi<\/h2>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Problem<\/th>\n<th>Scraping \/ Enterprise<\/th>\n<th>OddsPapi<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>NHL coverage<\/td>\n<td>Variable \u2014 depends on each book&#8217;s scraping defenses<\/td>\n<td>112 books on a single game (verified)<\/td>\n<\/tr>\n<tr>\n<td>Sharp lines (Pinnacle, Bet365)<\/td>\n<td>Pinnacle API is closed; Bet365 blocks scrapers<\/td>\n<td>Included on the free tier<\/td>\n<\/tr>\n<tr>\n<td>Player props<\/td>\n<td>Paywalled on most APIs<\/td>\n<td>Goals, Assists, Points, Shots on Goal \u2014 all native<\/td>\n<\/tr>\n<tr>\n<td>Historical puck lines<\/td>\n<td>Enterprise-only ($3k+\/month)<\/td>\n<td>Free tier includes <code>\/historical-odds<\/code><\/td>\n<\/tr>\n<tr>\n<td>Rate limits<\/td>\n<td>Get IP-banned after a few hundred requests<\/td>\n<td>~0.88s cooldown, no bans<\/td>\n<\/tr>\n<tr>\n<td>Setup time<\/td>\n<td>Weeks of scraper engineering<\/td>\n<td>10 minutes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Authenticate<\/h2>\n<p>OddsPapi uses a query-parameter API key, not a header. Grab a free key from the dashboard, then:<\/p>\n<pre class=\"wp-block-code\"><code>import requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\n# Smoke test\nr = requests.get(f\"{BASE_URL}\/sports\", params={\"apiKey\": API_KEY})\nprint(r.status_code)  # 200\n\nnhl = [s for s in r.json() if s[\"sportId\"] == 15]\nprint(nhl)\n# [{'sportId': 15, 'slug': 'ice-hockey', 'sportName': 'Ice Hockey'}]\n<\/code><\/pre>\n<p>Every call needs <code>apiKey<\/code> as a query parameter. Ice hockey is <strong>sportId=15<\/strong>. NHL is one tournament within that sport \u2014 we&#8217;ll filter for it in Step 2.<\/p>\n<h2>Step 2: Fetch NHL Fixtures<\/h2>\n<p>The <code>\/fixtures<\/code> endpoint returns every ice hockey game in a date range (max 10 days). Each fixture includes <code>tournamentName<\/code> and <code>categoryName<\/code>, so you can filter to NHL games only:<\/p>\n<pre class=\"wp-block-code\"><code>r = requests.get(\n    f\"{BASE_URL}\/fixtures\",\n    params={\n        \"apiKey\": API_KEY,\n        \"sportId\": 15,\n        \"from\": \"2026-04-18\",\n        \"to\": \"2026-04-28\",\n    },\n)\nfixtures = r.json()\n\nnhl = [\n    f for f in fixtures\n    if f.get(\"tournamentName\") == \"NHL\" and f.get(\"hasOdds\")\n]\nprint(f\"NHL games with odds: {len(nhl)}\")\n\nfor f in nhl[:5]:\n    print(f\"{f['participant1Name']:&lt;25} vs {f['participant2Name']:&lt;25} \"\n          f\"{f['startTime']}  ({f['fixtureId']})\")\n<\/code><\/pre>\n<p>Sample output:<\/p>\n<pre class=\"wp-block-code\"><code>NHL games with odds: 35\nCarolina Hurricanes       vs Ottawa Senators           2026-04-18T19:00:00.000Z  (id1500087370712566)\nDallas Stars              vs Minnesota Wild            2026-04-18T21:30:00.000Z  (id1500054070718042)\nEdmonton Oilers           vs Los Angeles Kings         2026-04-18T22:00:00.000Z  (...)\n<\/code><\/pre>\n<p>Two fields matter for filtering:<\/p>\n<ul>\n<li><code>tournamentName == \"NHL\"<\/code> \u2014 excludes AHL, KHL, international games, and college hockey<\/li>\n<li><code>hasOdds == True<\/code> \u2014 the fixture has at least one bookmaker price (otherwise <code>\/odds<\/code> returns metadata only)<\/li>\n<\/ul>\n<h2>Step 3: Pull Live Moneylines<\/h2>\n<p>The <code>\/odds<\/code> endpoint returns the current price from every bookmaker carrying the game. The response is deeply nested \u2014 bookmaker \u2192 market \u2192 outcome \u2192 players[&#8220;0&#8221;] \u2192 price. Here&#8217;s how to extract NHL moneylines across all books:<\/p>\n<pre class=\"wp-block-code\"><code>fixture_id = \"id1500023470558100\"  # Dallas vs Minnesota\n\nr = requests.get(\n    f\"{BASE_URL}\/odds\",\n    params={\"apiKey\": API_KEY, \"fixtureId\": fixture_id},\n)\ndata = r.json()\nbooks = data[\"bookmakerOdds\"]\nprint(f\"Books with odds: {len(books)}\")\n\n# Market 151 = \"Winner (incl. overtime and penalties)\" \u2014 NHL moneyline\n# Outcome 151 = Home (participant1), 152 = Away (participant2)\nMONEYLINE = \"151\"\n\nprint(f\"{'Book':&lt;18} {'Home':&lt;10} {'Away':&lt;10}\")\nfor slug, book in sorted(books.items()):\n    m = book.get(\"markets\", {}).get(MONEYLINE, {})\n    outs = m.get(\"outcomes\", {})\n    home = outs.get(\"151\", {}).get(\"players\", {}).get(\"0\", {}).get(\"price\")\n    away = outs.get(\"152\", {}).get(\"players\", {}).get(\"0\", {}).get(\"price\")\n    if home and away:\n        print(f\"{slug:&lt;18} {home:&lt;10} {away:&lt;10}\")\n<\/code><\/pre>\n<p>Live prices pulled just now for Dallas Stars vs Minnesota Wild:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Bookmaker<\/th>\n<th>Dallas (Home)<\/th>\n<th>Minnesota (Away)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>pinnacle<\/td>\n<td>1.847<\/td>\n<td>2.07<\/td>\n<\/tr>\n<tr>\n<td>bet365<\/td>\n<td>1.83<\/td>\n<td>2.00<\/td>\n<\/tr>\n<tr>\n<td>draftkings<\/td>\n<td>1.83<\/td>\n<td>2.00<\/td>\n<\/tr>\n<tr>\n<td>fanduel<\/td>\n<td>1.83<\/td>\n<td>2.00<\/td>\n<\/tr>\n<tr>\n<td>caesars<\/td>\n<td>1.833<\/td>\n<td>2.00<\/td>\n<\/tr>\n<tr>\n<td>betrivers<\/td>\n<td>1.82<\/td>\n<td>2.07<\/td>\n<\/tr>\n<tr>\n<td>bovada.lv<\/td>\n<td>1.82<\/td>\n<td>2.02<\/td>\n<\/tr>\n<tr>\n<td><strong>betfair-ex<\/strong><\/td>\n<td><strong>1.88<\/strong><\/td>\n<td><strong>2.10<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Note the line-shopping edge: <strong>Betfair Exchange is paying 1.88 on Dallas vs 1.82 at BetRivers<\/strong> \u2014 a 3.3% pricing gap on the same game. Running this query in a loop across your book list is the foundation of any value-betting system.<\/p>\n<p>Pinnacle sits between the US retail books (1.83) and the exchange (1.88). That&#8217;s the classic signature of a sharp market versus a retail book carrying a shaded line.<\/p>\n<h2>Step 4: Puck Lines (\u00b11.5 Goals)<\/h2>\n<p>The NHL standard puck line is \u00b11.5 goals, including overtime and shootout. OddsPapi uses two market IDs for the two sides:<\/p>\n<ul>\n<li><code>15228<\/code> \u2014 Handicap -1.5 (favorite giving 1.5 goals)<\/li>\n<li><code>15240<\/code> \u2014 Handicap +1.5 (underdog getting 1.5 goals)<\/li>\n<\/ul>\n<pre class=\"wp-block-code\"><code># Puck line -1.5 (favorite) and +1.5 (underdog)\nfor slug, book in books.items():\n    fav = book.get(\"markets\", {}).get(\"15228\", {}).get(\"outcomes\", {}).get(\n        \"15228\", {}).get(\"players\", {}).get(\"0\", {}).get(\"price\")\n    dog = book.get(\"markets\", {}).get(\"15240\", {}).get(\"outcomes\", {}).get(\n        \"15241\", {}).get(\"players\", {}).get(\"0\", {}).get(\"price\")\n    if fav or dog:\n        print(f\"{slug:&lt;18} DAL -1.5: {fav}   MIN +1.5: {dog}\")\n\n# Sample output:\n# pinnacle           DAL -1.5: 3.13   MIN +1.5: 3.6\n# bovada.lv          DAL -1.5: 3.1    MIN +1.5: 1.4\n<\/code><\/pre>\n<p>Puck lines have much thinner coverage than moneylines \u2014 only Pinnacle, Bovada, and a handful of others carried this game&#8217;s \u00b11.5 at query time. If you&#8217;re building a puck-line arb scanner, filter your book list to the ones that actually reliably post this market.<\/p>\n<h2>Step 5: Totals (Over\/Under)<\/h2>\n<p>NHL totals (incl. overtime) live under the <code>Total (incl. overtime and penalties)<\/code> market. Each line is its own market ID:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Line<\/th>\n<th>Market ID<\/th>\n<th>Over Outcome<\/th>\n<th>Under Outcome<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>5.5<\/td>\n<td>15174<\/td>\n<td>15174<\/td>\n<td>15175<\/td>\n<\/tr>\n<tr>\n<td>6.0<\/td>\n<td>15176<\/td>\n<td>15176<\/td>\n<td>15177<\/td>\n<\/tr>\n<tr>\n<td>6.5<\/td>\n<td>15178<\/td>\n<td>15178<\/td>\n<td>15179<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<pre class=\"wp-block-code\"><code>TOTALS = {\n    5.5: (15174, 15174, 15175),\n    6.0: (15176, 15176, 15177),\n    6.5: (15178, 15178, 15179),\n}\n\nfor slug in [\"pinnacle\", \"caesars\", \"draftkings\"]:\n    b = books.get(slug, {})\n    print(f\"\\n{slug}:\")\n    for line, (mid, over_oid, under_oid) in TOTALS.items():\n        m = b.get(\"markets\", {}).get(str(mid), {})\n        over = m.get(\"outcomes\", {}).get(str(over_oid), {}).get(\n            \"players\", {}).get(\"0\", {}).get(\"price\")\n        under = m.get(\"outcomes\", {}).get(str(under_oid), {}).get(\n            \"players\", {}).get(\"0\", {}).get(\"price\")\n        if over and under:\n            print(f\"  O\/U {line}: {over} \/ {under}\")\n\n# Sample output:\n# pinnacle:\n#   O\/U 5.5: 1.854 \/ 2.05\n#   O\/U 6.0: 2.04 \/ 1.819\n#   O\/U 6.5: 2.27 \/ 1.671\n# caesars:\n#   O\/U 6.0: 2.0 \/ 1.769\n#   O\/U 6.5: 2.2 \/ 1.667\n<\/code><\/pre>\n<p>Pinnacle&#8217;s middle total is 6.0 pricing roughly even juice (2.04 \/ 1.819), which tells you the model thinks this is a ~6-goal game. Caesars agrees on the number but slightly different juice. If you&#8217;re a totals bettor, Pinnacle is your benchmark \u2014 deviations from its line signal where the soft books are off.<\/p>\n<h2>Step 6: Player Props<\/h2>\n<p>This is where most generic odds APIs fall off a cliff. OddsPapi returns NHL player props natively \u2014 Goals, Assists, Points, Shots on Goal, Blocks, Saves, Power Play Points \u2014 with player names embedded in the response.<\/p>\n<p>Player prop markets use the <code>players<\/code> dict differently: each player is their own key (not just <code>\"0\"<\/code>):<\/p>\n<pre class=\"wp-block-code\"><code># Market 15140 = Over Under Player Shots On Goal, line 2.5 (incl. overtime)\n# Outcome 15140 = Over, 15141 = Under\nSHOTS_OVER_25 = \"15140\"\n\npin = books[\"pinnacle\"][\"markets\"][SHOTS_OVER_25][\"outcomes\"]\n\nprint(f\"{'Player':&lt;28} {'Over 2.5':&lt;10} {'Under 2.5':&lt;10}\")\nover_players = pin[\"15140\"][\"players\"]    # dict of {player_id: {...}}\nunder_players = pin[\"15141\"][\"players\"]\n\nfor pid, over in over_players.items():\n    if not over.get(\"active\"):\n        continue\n    under = under_players.get(pid, {})\n    name = over.get(\"playerName\", \"?\")\n    print(f\"{name:&lt;28} {over['price']:&lt;10} {under.get('price', '-'):&lt;10}\")\n<\/code><\/pre>\n<p>Live Pinnacle shots-on-goal props for Dallas-Minnesota:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Player<\/th>\n<th>Over 2.5 SOG<\/th>\n<th>Under 2.5 SOG<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Eriksson Ek, Joel<\/td>\n<td>1.833<\/td>\n<td>1.909<\/td>\n<\/tr>\n<tr>\n<td>Hartman, Ryan<\/td>\n<td>2.17<\/td>\n<td>1.641<\/td>\n<\/tr>\n<tr>\n<td>Rantanen, Mikko<\/td>\n<td>2.06<\/td>\n<td>1.709<\/td>\n<\/tr>\n<tr>\n<td>Boldy, Matt<\/td>\n<td>1.54<\/td>\n<td>2.37<\/td>\n<\/tr>\n<tr>\n<td>Johnston, Wyatt<\/td>\n<td>1.769<\/td>\n<td>1.98<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Core NHL player-prop markets worth hardcoding:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Market ID<\/th>\n<th>Description<\/th>\n<th>Handicap<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>15124<\/td>\n<td>Goals \u2014 Anytime Goalscorer<\/td>\n<td>0.5<\/td>\n<\/tr>\n<tr>\n<td>15118<\/td>\n<td>Assists<\/td>\n<td>0.5<\/td>\n<\/tr>\n<tr>\n<td>15130<\/td>\n<td>Points (G + A)<\/td>\n<td>0.5<\/td>\n<\/tr>\n<tr>\n<td>15138<\/td>\n<td>Shots on Goal<\/td>\n<td>1.5<\/td>\n<\/tr>\n<tr>\n<td>15140<\/td>\n<td>Shots on Goal<\/td>\n<td>2.5<\/td>\n<\/tr>\n<tr>\n<td>15142<\/td>\n<td>Shots on Goal<\/td>\n<td>3.5<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Don&#8217;t hardcode everything. The full NHL market catalog lives at <code>\/v4\/markets?sportId=15<\/code> (filter the response to <code>sportId == 15<\/code> \u2014 the endpoint returns cross-sport data by default):<\/p>\n<pre class=\"wp-block-code\"><code>r = requests.get(f\"{BASE_URL}\/markets\", params={\"apiKey\": API_KEY, \"sportId\": 15})\ncat = [m for m in r.json() if m[\"sportId\"] == 15]\n\nprint(f\"Total NHL markets: {len(cat)}\")\n# Total NHL markets: 745\n\n# Build a lookup for any market ID you see in the odds response\nmarket_names = {\n    m[\"marketId\"]: f\"{m['marketName']} (hcap={m['handicap']})\"\n    for m in cat\n}\n\n# Look up an unknown market ID from the odds response\nprint(market_names[15140])\n# 'Over Under Player Shots On Goal (incl. overtime) (hcap=2.5)'\n<\/code><\/pre>\n<h2>Step 7: Find the Best NHL Line Across 112 Books<\/h2>\n<p>Put it all together \u2014 a simple best-price scanner across every book carrying the NHL moneyline:<\/p>\n<pre class=\"wp-block-code\"><code>def best_price(books, market_id, outcome_id):\n    best_book, best_price = None, 0\n    for slug, b in books.items():\n        p = (b.get(\"markets\", {})\n              .get(str(market_id), {})\n              .get(\"outcomes\", {})\n              .get(str(outcome_id), {})\n              .get(\"players\", {})\n              .get(\"0\", {})\n              .get(\"price\"))\n        if p and p &gt; best_price:\n            best_book, best_price = slug, p\n    return best_book, best_price\n\n# Best Dallas moneyline across all 112 books\nhome_book, home_odds = best_price(books, 151, 151)\naway_book, away_odds = best_price(books, 151, 152)\n\nprint(f\"Best Dallas: {home_odds} at {home_book}\")\nprint(f\"Best Minnesota: {away_odds} at {away_book}\")\n\n# Sample output:\n# Best Dallas: 1.88 at betfair-ex\n# Best Minnesota: 2.10 at betfair-ex\n<\/code><\/pre>\n<p>Now check for an arb. Implied probability = 1 \/ decimal_odds. If the sum of the two sides&#8217; best-price implied probs is below 1.0, you have a risk-free opportunity:<\/p>\n<pre class=\"wp-block-code\"><code>arb = (1 \/ home_odds) + (1 \/ away_odds)\nprint(f\"Book sum: {arb:.4f}\")\n# Book sum: 1.0083  \u2192 0.83% overround, not an arb but tight\n<\/code><\/pre>\n<p>For a full arb scanner with multi-outcome staking math, see our dedicated <a href=\"https:\/\/oddspapi.io\/blog\/arbitrage-betting-bot-python\/\">Python arbitrage bot tutorial<\/a>.<\/p>\n<h2>Historical NHL Odds (Free)<\/h2>\n<p>Every sharp bettor needs Closing Line Value. OddsPapi&#8217;s <code>\/historical-odds<\/code> endpoint returns the full price history for any NHL fixture, free. The response shape differs from live odds \u2014 <code>players[\"0\"]<\/code> is a <strong>list<\/strong> of snapshots, not a dict:<\/p>\n<pre class=\"wp-block-code\"><code>r = requests.get(\n    f\"{BASE_URL}\/historical-odds\",\n    params={\n        \"apiKey\": API_KEY,\n        \"fixtureId\": fixture_id,\n        \"bookmakers\": \"pinnacle,bet365,singbet\",  # max 3 per call\n    },\n)\nhist = r.json()\n\n# Walk the price history for the Dallas moneyline at Pinnacle\nfor snap in hist[\"bookmakers\"][\"pinnacle\"][\"markets\"][\"151\"][\"outcomes\"][\"151\"][\"players\"][\"0\"]:\n    print(f\"{snap['createdAt']}  {snap['price']}\")\n\n# Sample output:\n# 2026-04-17T14:12:03+00:00  1.92\n# 2026-04-17T18:30:41+00:00  1.89\n# 2026-04-18T09:02:15+00:00  1.85\n# 2026-04-18T14:07:06+00:00  1.847   \u2190 current\n<\/code><\/pre>\n<p>That&#8217;s steam money moving the Dallas line from 1.92 to 1.85 in 24 hours \u2014 exactly the kind of signal a line-movement alert bot looks for. For a full steam-move detector, see the <a href=\"https:\/\/oddspapi.io\/blog\/steam-move-detector-python\/\">steam move detector post<\/a>.<\/p>\n<h2>Rate Limits &amp; Production Notes<\/h2>\n<ul>\n<li><strong>Cooldown:<\/strong> ~0.88s between calls to the same endpoint. Use <code>time.sleep(0.2)<\/code> between iterations as a safety buffer.<\/li>\n<li><strong>Date range:<\/strong> <code>\/fixtures<\/code> accepts a max 10-day window. Loop across windows for season-long backfills.<\/li>\n<li><strong>Historical bookmakers:<\/strong> <code>\/historical-odds<\/code> caps at 3 books per call. Loop with different combinations if you need more.<\/li>\n<li><strong>Active outcomes:<\/strong> check <code>outcome.active == True<\/code> \u2014 suspended markets ship with <code>active: false<\/code> and stale prices.<\/li>\n<li><strong>Exchange odds:<\/strong> Betfair Exchange and Polymarket are included. Their payloads add an <code>exchangeMeta<\/code> block with the lay side. Parse it defensively \u2014 the shape varies by book.<\/li>\n<\/ul>\n<h2>Why This Beats the Alternative<\/h2>\n<p>Every other NHL odds option forces a tradeoff:<\/p>\n<ul>\n<li><strong>The Odds API:<\/strong> 20 bookmakers, $119\/mo for 5M calls, no player props on the base plan.<\/li>\n<li><strong>Sportradar:<\/strong> Enterprise-only, $3k+\/month, 6-week contract cycle.<\/li>\n<li><strong>SportsGameOdds:<\/strong> 85 books, paywalls historical data, no sharp-book depth.<\/li>\n<li><strong>Scraping DraftKings directly:<\/strong> IP bans within hours, TOS violation.<\/li>\n<\/ul>\n<p>OddsPapi is the only option that gives you Pinnacle + Bet365 + DraftKings + FanDuel + Betfair Exchange + 107 other books on one endpoint, on a free tier, with free historical data. That&#8217;s the pitch.<\/p>\n<h2>Stop Scraping. Start Building.<\/h2>\n<p><a href=\"https:\/\/oddspapi.io\"><strong>Grab a free API key<\/strong><\/a> and you&#8217;re pulling NHL puck lines, totals, and shots-on-goal props in the next 10 minutes. Historical data is included \u2014 backtest your model before you risk a dollar.<\/p>\n<h2>FAQ<\/h2>\n<h3>Is there an official NHL odds API?<\/h3>\n<p>No. The NHL licenses official data partnerships for sports integrity and betting operators, but does not expose a public developer API for odds. OddsPapi aggregates odds from 350+ bookmakers covering every NHL regular-season and playoff game.<\/p>\n<h3>Does OddsPapi include DraftKings and FanDuel NHL odds?<\/h3>\n<p>Yes. Both DraftKings and FanDuel are covered on every NHL fixture we checked, alongside BetMGM, Caesars, BetRivers, Bovada, and the Betfair Exchange. Sharp books (Pinnacle, Bet365, Singbet) are also included on the free tier.<\/p>\n<h3>Are NHL player props available?<\/h3>\n<p>Yes. Goals, Assists, Points, Shots on Goal, Blocks, Power Play Points, Saves, and Goals Against are all native markets. Player names are embedded in the response \u2014 no extra lookup required.<\/p>\n<h3>Is historical NHL odds data free?<\/h3>\n<p>Yes. The <code>\/historical-odds<\/code> endpoint is on the free tier. Every price change is timestamped, so you can compute Closing Line Value, track steam moves, or backtest any strategy against real NHL line history.<\/p>\n<h3>What&#8217;s the rate limit?<\/h3>\n<p>Approximately 0.88 seconds between calls to the same endpoint on the free tier. For production arb scanners or line monitors, OddsPapi also offers WebSocket push so you don&#8217;t have to poll.<\/p>\n<p><!--\nFocus Keyphrase: NHL odds API\nSEO Title: NHL Odds API: Puck Lines, Totals & Player Props (Python + Free Tier)\nMeta Description: Pull live NHL puck lines, totals, player props, and historical odds from 350+ bookmakers including Pinnacle, DraftKings, FanDuel. Python tutorial, free API.\nSlug: nhl-odds-api-puck-lines-player-props\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pull live NHL puck lines, totals, player props &#038; historical odds from 350+ bookmakers including Pinnacle, DraftKings, FanDuel. Python tutorial, free API.<\/p>\n","protected":false},"author":2,"featured_media":2893,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[8,58,9,51,11],"class_list":["post-2892","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-free-api","tag-nhl","tag-odds-api","tag-player-props","tag-python"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>NHL Odds API: Puck Lines, Totals &amp; Player Props (Python + Free Tier) | Odds API Development Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"NHL Odds API: Puck Lines, Totals &amp; Player Props (Python + Free Tier) | Odds API Development Blog\" \/>\n<meta property=\"og:description\" content=\"Pull live NHL puck lines, totals, player props &amp; historical odds from 350+ bookmakers including Pinnacle, DraftKings, FanDuel. Python tutorial, free API.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\" \/>\n<meta property=\"og:site_name\" content=\"Odds API Development Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-28T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1429\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Odds API Writer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/oddspapi.io\/logo-v2.webp\" \/>\n<meta name=\"twitter:creator\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:site\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Odds API Writer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"NHL Odds API: Puck Lines, Totals &#038; Player Props (Python + Free Tier)\",\"datePublished\":\"2026-04-28T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\"},\"wordCount\":1301,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp\",\"keywords\":[\"Free API\",\"NHL\",\"Odds API\",\"Player Props\",\"Python\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\",\"name\":\"NHL Odds API: Puck Lines, Totals & Player Props (Python + Free Tier) | Odds API Development Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp\",\"datePublished\":\"2026-04-28T10:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp\",\"width\":2560,\"height\":1429,\"caption\":\"NHL Odds API - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"NHL Odds API: Puck Lines, Totals &#038; Player Props (Python + Free Tier)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"name\":\"OddsPapi\",\"description\":\"Sports Odds APIs Tutorials &amp; Guides\",\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"alternateName\":\"Odds Papi\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/oddspapi.io\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\",\"name\":\"OddsPapi\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"width\":135,\"height\":135,\"caption\":\"OddsPapi\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/oddspapiapi\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\",\"name\":\"Odds API Writer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"caption\":\"Odds API Writer\"},\"url\":\"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"NHL Odds API: Puck Lines, Totals & Player Props (Python + Free Tier) | Odds API Development Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/","og_locale":"en_US","og_type":"article","og_title":"NHL Odds API: Puck Lines, Totals & Player Props (Python + Free Tier) | Odds API Development Blog","og_description":"Pull live NHL puck lines, totals, player props & historical odds from 350+ bookmakers including Pinnacle, DraftKings, FanDuel. Python tutorial, free API.","og_url":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/","og_site_name":"Odds API Development Blog","article_published_time":"2026-04-28T10:00:00+00:00","og_image":[{"width":2560,"height":1429,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp","type":"image\/webp"}],"author":"Odds API Writer","twitter_card":"summary_large_image","twitter_image":"https:\/\/oddspapi.io\/logo-v2.webp","twitter_creator":"@oddspapiapi","twitter_site":"@oddspapiapi","twitter_misc":{"Written by":"Odds API Writer","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"NHL Odds API: Puck Lines, Totals &#038; Player Props (Python + Free Tier)","datePublished":"2026-04-28T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/"},"wordCount":1301,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp","keywords":["Free API","NHL","Odds API","Player Props","Python"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/","url":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/","name":"NHL Odds API: Puck Lines, Totals & Player Props (Python + Free Tier) | Odds API Development Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp","datePublished":"2026-04-28T10:00:00+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/04\/nhl-odds-api-puck-lines-player-props-scaled.webp","width":2560,"height":1429,"caption":"NHL Odds API - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"NHL Odds API: Puck Lines, Totals &#038; Player Props (Python + Free Tier)"}]},{"@type":"WebSite","@id":"https:\/\/oddspapi.io\/blog\/#website","url":"https:\/\/oddspapi.io\/blog\/","name":"OddsPapi","description":"Sports Odds APIs Tutorials &amp; Guides","publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"alternateName":"Odds Papi","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oddspapi.io\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/oddspapi.io\/blog\/#organization","name":"OddsPapi","url":"https:\/\/oddspapi.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","width":135,"height":135,"caption":"OddsPapi"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/oddspapiapi"]},{"@type":"Person","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13","name":"Odds API Writer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","caption":"Odds API Writer"},"url":"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/"}]}},"_links":{"self":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/2892","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/comments?post=2892"}],"version-history":[{"count":1,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/2892\/revisions"}],"predecessor-version":[{"id":2894,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/2892\/revisions\/2894"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/2893"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=2892"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=2892"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=2892"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}