{"id":4260,"date":"2026-09-29T10:00:00","date_gmt":"2026-09-29T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=4260"},"modified":"2026-09-18T13:25:47","modified_gmt":"2026-09-18T13:25:47","slug":"nhl-first-period-odds-api","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/","title":{"rendered":"NHL Odds API: 95 Books Price the First Period Before Puck Drop"},"content":{"rendered":"<p>The NHL season opens on 29 September 2026, and Carolina Hurricanes v Florida Panthers carries 225 bookmakers eleven days out. Most developers pull the moneyline, the puck line and the total off that board and stop. They walk past 95 market IDs.<\/p>\n<p>Those 95 IDs settle on a single period. First Period Result, Second Period Total, Third Period Handicap: a separate board with its own bookmakers, its own margins and its own clock. Every number below comes from a live call made on 18 September 2026 at 13:19 UTC.<\/p>\n<h2>The Period Board Is a Third of the Catalogue<\/h2>\n<p>One <code>\/v4\/odds<\/code> call on the opener returns 270 market IDs. Split them on the <code>period<\/code> field from <code>\/v4\/markets<\/code> and the board separates into five stacks.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th><code>period<\/code><\/th>\n<th>Settles on<\/th>\n<th>Market IDs<\/th>\n<th>Bookmakers<\/th>\n<th>Prices<\/th>\n<th>Live<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>fulltime<\/code><\/td>\n<td>the score after 60 minutes<\/td>\n<td>97<\/td>\n<td>209<\/td>\n<td>9,642<\/td>\n<td>65.9%<\/td>\n<\/tr>\n<tr>\n<td><code>result<\/code><\/td>\n<td>the final score, overtime and shootout included<\/td>\n<td>77<\/td>\n<td>207<\/td>\n<td>4,902<\/td>\n<td>45.3%<\/td>\n<\/tr>\n<tr>\n<td><code>p1<\/code><\/td>\n<td>the first period alone<\/td>\n<td>35<\/td>\n<td>95<\/td>\n<td>1,516<\/td>\n<td>93.6%<\/td>\n<\/tr>\n<tr>\n<td><code>p2<\/code><\/td>\n<td>the second period alone<\/td>\n<td>30<\/td>\n<td>55<\/td>\n<td>816<\/td>\n<td>99.1%<\/td>\n<\/tr>\n<tr>\n<td><code>p3<\/code><\/td>\n<td>the third period alone<\/td>\n<td>30<\/td>\n<td>55<\/td>\n<td>736<\/td>\n<td>99.7%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Read the Live column again. The two full-game stacks run 66% and 45% live. The three period stacks run 94%, 99% and 100%. The full-game side is mostly alternate-line ladder, 31 handicap rungs and 25 total rungs deep, and a book posts the whole ladder while quoting two or three rungs of it. The period board carries no deep ladder, so almost every rung on it is a price you can act on.<\/p>\n<p>One more ID sits outside the five stacks. <code>151276<\/code> Will There Be Overtime ships with <code>period: null<\/code>, quoted by 60 books at 98.3% live. Guard your period lookup against a null.<\/p>\n<h3>Old Way vs OddsPapi<\/h3>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Job<\/th>\n<th>Scraping or a single-book feed<\/th>\n<th>OddsPapi<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Find every first-period market<\/td>\n<td>Read one sportsbook&#8217;s markup and guess the rest<\/td>\n<td>Filter 270 IDs on <code>period == \"p1\"<\/code><\/td>\n<\/tr>\n<tr>\n<td>Rank the first-period price<\/td>\n<td>2 or 3 books<\/td>\n<td>95 books on one fixture<\/td>\n<\/tr>\n<tr>\n<td>Know which clock a market settles on<\/td>\n<td>Parse the market&#8217;s display name<\/td>\n<td>Read the <code>period<\/code> field<\/td>\n<\/tr>\n<tr>\n<td>Grade it after the final horn<\/td>\n<td>Scrape a scoreboard<\/td>\n<td><code>\/v4\/settlements<\/code> returns WIN, LOSE or PUSH per outcome<\/td>\n<\/tr>\n<tr>\n<td>Price<\/td>\n<td>Proxies, breakage, legal risk<\/td>\n<td>Free tier, 250 requests a month, no card<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Find the Fixtures<\/h2>\n<p>Ice hockey is <code>sportId<\/code> 15 and the NHL is <code>tournamentId<\/code> 234. Filter on both. The catalogue holds two <code>extraliga<\/code> tournaments, and <code>\/v4\/fixtures<\/code> returns HTTP 400 on a date range wider than ten days when <code>sportId<\/code> is your only filter.<\/p>\n<pre class=\"wp-block-code\"><code>import os, requests\n\nAPI_KEY = os.environ[\"ODDSPAPI_API_KEY\"]\nBASE = \"https:\/\/api.oddspapi.io\/v4\"\n\nr = requests.get(f\"{BASE}\/fixtures\", params={\n    \"apiKey\": API_KEY,\n    \"sportId\": 15,          # Ice Hockey\n    \"tournamentId\": 234,    # NHL\n    \"from\": \"2026-09-29\",\n    \"to\": \"2026-10-05\",\n})\nr.raise_for_status()\nfixtures = r.json()\n\nfor f in sorted(fixtures, key=lambda x: x[\"startTime\"])[:5]:\n    print(f[\"fixtureId\"], f[\"startTime\"],\n          f[\"participant1Name\"], \"v\", f[\"participant2Name\"])\nprint(f\"{len(fixtures)} fixtures, {sum(1 for f in fixtures if f['hasOdds'])} priced\")<\/code><\/pre>\n<p>Opening week returns 38 fixtures and all 38 are priced:<\/p>\n<pre class=\"wp-block-code\"><code>id1500023472886402 2026-09-29T21:00:00.000Z Carolina Hurricanes v Florida Panthers\nid1500023472886404 2026-09-29T23:00:00.000Z Toronto Maple Leafs v Montreal Canadiens\nid1500023472886406 2026-09-30T00:00:00.000Z Boston Bruins v New York Rangers\nid1500023472886408 2026-09-30T02:00:00.000Z Edmonton Oilers v Vancouver Canucks\nid1500023472886410 2026-09-30T02:30:00.000Z Vegas Golden Knights v Chicago Blackhawks\n38 fixtures, 38 priced<\/code><\/pre>\n<h2>Step 2: Split the Board on the Period Field<\/h2>\n<p><code>\/v4\/markets<\/code> ignores <code>sportId<\/code> and returns all 33,115 definitions, so pull it once and index it in memory. Check the status code before you read the payload. A 429 body is valid JSON, so a check on the payload alone reads a rate limit as an empty board. Give the helper a plain exponential backoff on 5xx too, and the same function serves every endpoint in this post.<\/p>\n<pre class=\"wp-block-code\"><code>import os, time, requests\nfrom collections import defaultdict\n\nAPI_KEY = os.environ[\"ODDSPAPI_API_KEY\"]\nBASE = \"https:\/\/api.oddspapi.io\/v4\"\nFIXTURE = \"id1500023472886402\"   # Carolina Hurricanes v Florida Panthers\n\ndef call(path, **params):\n    \"\"\"Retry a 429 on the body's retryMs, and back off on a transient 5xx.\"\"\"\n    params[\"apiKey\"] = API_KEY\n    for attempt in range(5):\n        r = requests.get(f\"{BASE}\/{path}\", params=params, timeout=300)\n        if r.status_code == 429:\n            time.sleep(r.json()[\"error\"].get(\"retryMs\", 1500) \/ 1000 + 0.5)\n            continue\n        if r.status_code &gt;= 500:\n            time.sleep(2 ** attempt)\n            continue\n        r.raise_for_status()\n        return r.json()\n    raise RuntimeError(f\"{path} did not answer\")\n\n# \/markets ignores sportId and returns the whole catalogue, so index it once.\nMARKET = {str(m[\"marketId\"]): m for m in call(\"markets\")}\nboard = call(\"odds\", fixtureId=FIXTURE)\n\nstats = defaultdict(lambda: {\"ids\": set(), \"books\": set(), \"prices\": 0, \"live\": 0})\nfor slug, book in board[\"bookmakerOdds\"].items():\n    for market_id, market in book[\"markets\"].items():\n        row = stats[MARKET.get(market_id, {}).get(\"period\")]\n        row[\"ids\"].add(market_id)\n        row[\"books\"].add(slug)\n        for outcome in market[\"outcomes\"].values():\n            for price in outcome[\"players\"].values():\n                row[\"prices\"] += 1\n                row[\"live\"] += bool(price[\"active\"])\n\nprint(f\"{len(board['bookmakerOdds'])} bookmakers on the fixture\")\nprint(f\"{'period':10} {'ids':&gt;5} {'books':&gt;6} {'prices':&gt;7} {'live':&gt;7}\")\nfor period, row in sorted(stats.items(), key=lambda x: -x[1][\"prices\"]):\n    print(f\"{str(period):10} {len(row['ids']):5} {len(row['books']):6} \"\n          f\"{row['prices']:7} {row['live'] \/ row['prices']:6.1%}\")<\/code><\/pre>\n<p>That prints the table above. Do not resolve a period market by its name. First Period Total and Total both read as a totals market, they both carry a <code>handicap<\/code> of 1.5 or 6.5, and only the <code>period<\/code> field separates them. You hit the same problem on <a href=\"https:\/\/oddspapi.io\/blog\/cs2-odds-api\/\">CS2, where Maps Handicap is 15 market IDs sharing one name<\/a>.<\/p>\n<h2>Step 3: See Who Quotes Each Period<\/h2>\n<p>The three periods do not draw the same bookmakers. On the opener, 95 books quote at least one first-period market, 55 quote all three periods, and 40 quote the first period and nothing after it.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Population<\/th>\n<th>Books<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Quote the first period<\/td>\n<td>95<\/td>\n<\/tr>\n<tr>\n<td>Quote all three periods<\/td>\n<td>55<\/td>\n<\/tr>\n<tr>\n<td>Quote the first period only<\/td>\n<td>40<\/td>\n<\/tr>\n<tr>\n<td>On the fixture at all<\/td>\n<td>225<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Those 40 first-period-only books include <code>bovada.lv<\/code>, <code>bodog.eu<\/code>, <code>hardrockbet<\/code>, <code>playnow<\/code>, <code>bet99<\/code>, <code>betmgm.bet.br<\/code> and the Betano regional feeds. Build your bookmaker list per period, not per fixture. A list built off the moneyline board will send you looking for a third-period price that 170 of those books never posted.<\/p>\n<h2>Step 4: Rank the First-Period Board<\/h2>\n<p>Counting legs is the guard that matters here. A book that posts two legs of a three-way first-period market computes as a cheap quote and floats to the top of any margin ranking. Read <code>marketLength<\/code> off <code>\/v4\/markets<\/code> and drop anything that does not match.<\/p>\n<pre class=\"wp-block-code\"><code>legs_expected = MARKET[\"151278\"][\"marketLength\"]   # First Period Result, 3 legs\nquotes = []\nfor slug, book in board[\"bookmakerOdds\"].items():\n    market = book[\"markets\"].get(\"151278\")\n    if not market:\n        continue\n    legs = {}\n    for outcome_id, outcome in market[\"outcomes\"].items():\n        for price in outcome[\"players\"].values():\n            if not price[\"active\"] or not price[\"price\"]:\n                legs = {}\n                break\n            legs[outcome_id] = price[\"price\"]\n    # A partial quote is not a cheap quote. Count the legs before you rank.\n    if len(legs) == legs_expected:\n        quotes.append((slug, sum(1 \/ p for p in legs.values()) - 1,\n                       tuple(sorted(legs.items()))))\n\nquotes.sort(key=lambda q: q[1])\ntuples = {q[2] for q in quotes}\nprint(f\"{len(quotes)} complete quotes, {len(tuples)} distinct price tuples \"\n      f\"({1 - len(tuples) \/ len(quotes):.1%} collapse)\")\nprint(f\"median margin {statistics.median(q[1] for q in quotes):.2%}\")\nfor slug, margin, _ in quotes[:5]:\n    print(f\"  {slug:16} {margin:.2%}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>57 complete quotes, 15 distinct price tuples (73.7% collapse)\nmedian margin 9.68%\n  inbet            4.76%\n  20bet            7.30%\n  ivibet           7.30%\n  stake            7.30%\n  tonybet          7.30%<\/code><\/pre>\n<p>Fifty-seven books, fifteen opinions. The dedupe collapse on First Period Result is 73.7%, and on First Period Total 1.5 it reaches 80.8%. Distinct slugs quote byte-identical prices, and <code>cloneOf<\/code> does not flag them, so collapse on the price tuple before you average anything. The same discipline runs through <a href=\"https:\/\/oddspapi.io\/blog\/consensus-odds-fair-odds-calculator-python\/\">building a consensus line from 350+ bookmakers<\/a>.<\/p>\n<p>The period board costs more than the full game, and the gap is consistent across the three periods:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Market<\/th>\n<th><code>period<\/code><\/th>\n<th>Complete quotes<\/th>\n<th>Median margin<\/th>\n<th>Tightest<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>151 Winner (incl. overtime and penalties)<\/td>\n<td><code>result<\/code><\/td>\n<td>188<\/td>\n<td>5.49%<\/td>\n<td><code>duel<\/code> 2.89%<\/td>\n<\/tr>\n<tr>\n<td>153 Regular Time Result<\/td>\n<td><code>fulltime<\/code><\/td>\n<td>178<\/td>\n<td>7.72%<\/td>\n<td><code>1xbet<\/code> 3.12%<\/td>\n<\/tr>\n<tr>\n<td>151287 First Period Total 1.5<\/td>\n<td><code>p1<\/code><\/td>\n<td>78<\/td>\n<td>8.82%<\/td>\n<td><code>caesars<\/code> 3.95%<\/td>\n<\/tr>\n<tr>\n<td>151278 First Period Result<\/td>\n<td><code>p1<\/code><\/td>\n<td>57<\/td>\n<td>9.68%<\/td>\n<td><code>inbet<\/code> 4.76%<\/td>\n<\/tr>\n<tr>\n<td>151644 Second Period Result<\/td>\n<td><code>p2<\/code><\/td>\n<td>44<\/td>\n<td>9.87%<\/td>\n<td><code>inbet<\/code> 4.07%<\/td>\n<\/tr>\n<tr>\n<td>152010 Third Period Result<\/td>\n<td><code>p3<\/code><\/td>\n<td>44<\/td>\n<td>9.95%<\/td>\n<td><code>inbet<\/code> 4.15%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>A first-period three-way costs about two points of margin over the same fixture&#8217;s regular-time three-way and four over the two-way. The spread inside each market is wider still: <code>caesars<\/code> and the two William Hill feeds quote First Period Total at 3.95% against a 8.82% board median. Shop it. Our <a href=\"https:\/\/oddspapi.io\/blog\/line-shopping-python-best-odds\/\">line-shopping walkthrough<\/a> runs the same comparison across the full catalogue.<\/p>\n<h2>Step 5: Read the Clock Right<\/h2>\n<p><code>\/v4\/scores<\/code> returns a <code>periods<\/code> object, and the keys inside it do not all mean the same kind of number. <code>p1<\/code>, <code>p2<\/code> and <code>p3<\/code> are per-period deltas. <code>fulltime<\/code>, <code>overtime<\/code> and <code>result<\/code> are running totals.<\/p>\n<pre class=\"wp-block-code\"><code>def line(periods, key):\n    p = periods.get(key)\n    return None if p is None else (p[\"participant1Score\"], p[\"participant2Score\"])\n\nplayed = call(\"fixtures\", sportId=15, tournamentId=234,\n              **{\"from\": \"2026-04-05\", \"to\": \"2026-04-13\"})\n\nregulation = overtime_goal = shootout = mismatch = counted = 0\nfor fixture in played:\n    periods = call(\"scores\", fixtureId=fixture[\"fixtureId\"])[\"scores\"][\"periods\"]\n    time.sleep(1.15)\n    if not all(k in periods for k in (\"p1\", \"p2\", \"p3\", \"fulltime\", \"result\")):\n        continue\n    counted += 1\n    # p1, p2 and p3 are per-period deltas. fulltime is the running regulation score.\n    summed = tuple(sum(periods[k][f\"participant{n}Score\"] for k in (\"p1\", \"p2\", \"p3\"))\n                   for n in (1, 2))\n    if summed != line(periods, \"fulltime\"):\n        mismatch += 1\n    if line(periods, \"result\") == line(periods, \"fulltime\"):\n        regulation += 1\n    elif line(periods, \"overtime\") == line(periods, \"result\"):\n        overtime_goal += 1   # someone scored in the extra period\n    else:\n        shootout += 1        # overtime stayed level, the shootout decided it\n\nprint(f\"{counted} played fixtures, {mismatch} where p1+p2+p3 != fulltime\")\nprint(f\"regulation {regulation}, overtime goal {overtime_goal}, shootout {shootout}\")\nprint(f\"past regulation {(overtime_goal + shootout) \/ counted:.1%}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>63 played fixtures, 0 where p1+p2+p3 != fulltime\nregulation 51, overtime goal 7, shootout 5\npast regulation 19.0%<\/code><\/pre>\n<p>Across 63 played NHL fixtures from 5 to 12 April 2026 the three period deltas summed to the regulation score every single time. That gives you two free consistency checks. Sum the periods and compare against <code>fulltime<\/code> before you trust a scoreline. Then diff <code>result<\/code> against <code>overtime<\/code>: when they match, somebody scored in the extra period; when <code>overtime<\/code> still sits level and <code>result<\/code> moved, the shootout decided it. Twelve of the 63 went past regulation, seven on an overtime goal and five in a shootout.<\/p>\n<h2>Step 6: Prove the Grading<\/h2>\n<p>Take a played fixture and read the period scores next to the settled grid. This is <code>id1500023462079028<\/code> from 5 April 2026.<\/p>\n<pre class=\"wp-block-code\"><code>MARKET = {str(m[\"marketId\"]): m for m in call(\"markets\")}\nperiods = call(\"scores\", fixtureId=\"id1500023462079028\")[\"scores\"][\"periods\"]\nfor key in (\"p1\", \"p2\", \"p3\", \"fulltime\", \"result\"):\n    score = periods[key]\n    print(f\"  {key:9} {score['participant1Score']}-{score['participant2Score']}\")\n\ngraded = call(\"settlements\", fixtureId=\"id1500023462079028\")[\"markets\"]\nfor market_id in (\"151278\", \"151287\", \"151644\", \"152010\", \"153\", \"151\"):\n    meta = MARKET[market_id]\n    results = {\n        out[\"outcomeName\"]:\n            list(graded[market_id][\"outcomes\"][str(out[\"outcomeId\"])][\"players\"].values())[0][\"result\"]\n        for out in meta[\"outcomes\"]\n    }\n    print(f\"{market_id:8} {meta['period']:9} {meta['marketName'][:22]:22} {results}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>  p1        1-1\n  p2        0-3\n  p3        2-1\n  fulltime  3-5\n  result    3-5\n151278   p1        First Period Result    {'2': 'LOSE', 'X': 'WIN', '1': 'LOSE'}\n151287   p1        First Period Total     {'Over': 'WIN', 'Under': 'LOSE'}\n151644   p2        Second Period Result   {'X': 'LOSE', '1': 'LOSE', '2': 'WIN'}\n152010   p3        Third Period Result    {'X': 'LOSE', '2': 'LOSE', '1': 'WIN'}\n153      fulltime  Regular Time Result    {'1': 'LOSE', '2': 'WIN', 'X': 'LOSE'}\n151      result    Winner (incl. overtime {'1': 'LOSE', '2': 'WIN'}<\/code><\/pre>\n<p>Every grade lands on its own period. The first period finished 1-1, so First Period Result pays the draw and First Period Total 1.5 pays the over on two goals. The second period went 0-3, so the away leg wins there, while the home leg wins the third on 2-1. Regular Time Result reads the 3-5 aggregate and Winner reads the same. Five clocks on one fixture, and four of them disagree with the scoreline you would print on a scoreboard.<\/p>\n<p>The grid holds 634 markets and returns 995 decided outcomes on this fixture. The period markets account for 540 of them, 180 on each of the three periods, against 228 on <code>fulltime<\/code> and 227 on <code>result<\/code>. More than half the graded surface of an NHL fixture sits on the period board.<\/p>\n<h2>Where the Sharp Anchor Is<\/h2>\n<p>Eleven days out, <code>pinnacle<\/code> and <code>kalshi<\/code> carry no price on the opener. Pass <code>bookmakers=pinnacle<\/code> on its own and you get HTTP 200 with the slug missing from <code>bookmakerOdds<\/code>, which means unpriced rather than gated; a slug that does not exist returns HTTP 400 instead. <code>betfair-ex<\/code> and <code>polymarket<\/code> are on the board but quote full-game markets only, and neither posts a period market. <code>draftkings<\/code> is the one major that does.<\/p>\n<p>So de-vig the deduped consensus rather than leaning on a sharp reference that has not opened yet. Fifteen distinct tuples across 57 books is a thin sample, and it gets thicker every day as the season approaches. Re-measure on the night.<\/p>\n<h2>Where This Sits Next to the Other Two NHL Traps<\/h2>\n<p>Hockey holds three market-ID traps. The period board is one. <a href=\"https:\/\/oddspapi.io\/blog\/nhl-regulation-overtime-markets\/\">The 118 twin markets<\/a> are the second: every full-game handicap and total ships twice, once settling on regulation and once on the final score, with the same <code>handicap<\/code> value on both. For the third, learn the menu from <a href=\"https:\/\/oddspapi.io\/blog\/nhl-odds-api-puck-lines-player-props\/\">the puck lines and totals guide<\/a>.<\/p>\n<p>Read together, the rule is one line: in ice hockey, <code>period<\/code> is the primary key and <code>marketName<\/code> is a label. Basketball splits its result the same way across two market IDs, and you guard it with the same flags you use on <a href=\"https:\/\/oddspapi.io\/blog\/nba-opening-night-odds-api\/\">the NBA opening-night board<\/a>.<\/p>\n<h2>Going Live<\/h2>\n<p>Poll <code>\/v4\/odds<\/code> at 10 requests a second on the odds endpoints and 200 a minute elsewhere, or take the WebSocket feed and stop polling. Between periods the first-period board closes and the second-period board opens, so a scheduler that refreshes on a fixed timer will hold a dead price for a full intermission. Track <code>changedAt<\/code> per price and treat a market whose <code>changedAt<\/code> has fallen behind the book&#8217;s newest stamp on the fixture as stale.<\/p>\n<p>One warning for anyone storing this. A book that withdraws a market keeps its last price with <code>active: true<\/code>. The flag tells you the book has not switched the rung off, not that the number is current. Compare timestamps.<\/p>\n<h2>Get Your Key<\/h2>\n<p>The free tier runs 250 requests a month across every sport and every bookmaker, with no card and no sales call. That covers a whole opening week of NHL period boards. <a href=\"https:\/\/oddspapi.io\/en\/sign-up\">Sign up<\/a>, export the key, and run Step 1 above.<\/p>\n<p>Historical odds come free as well, so the 63-fixture clock census in Step 5 costs nothing to reproduce on last season&#8217;s schedule. If you want the board pushed rather than polled, the WebSocket feed carries the same 225-book payload with resume cursors. <a href=\"https:\/\/oddspapi.io\/blog\/free-odds-api-350-bookmakers\/\">The free-tier walkthrough<\/a> has the setup.<\/p>\n<h2>FAQ<\/h2>\n<h3>How do I tell a first-period market from a full-game market?<\/h3>\n<p>Read the <code>period<\/code> field on <code>\/v4\/markets<\/code>. It returns <code>p1<\/code>, <code>p2<\/code> or <code>p3<\/code> for a single period, <code>fulltime<\/code> for the 60-minute score and <code>result<\/code> for the final score. The market name will not tell you, because First Period Total and Total both read as a totals market and can carry the same <code>handicap<\/code>.<\/p>\n<h3>How many bookmakers price NHL period markets?<\/h3>\n<p>On Carolina v Florida, measured 18 September 2026 at T-11d, 95 of the 225 bookmakers on the fixture quoted at least one first-period market and 55 quoted all three periods. Forty of them quoted the first period and no other, including <code>bovada.lv<\/code>, <code>hardrockbet<\/code> and <code>bet99<\/code>.<\/p>\n<h3>Do the period scores add up to the full-time score?<\/h3>\n<p>Yes. Across 63 played NHL fixtures from 5 to 12 April 2026, <code>p1<\/code> plus <code>p2<\/code> plus <code>p3<\/code> equalled the <code>fulltime<\/code> line on all 63. The period keys are deltas; <code>fulltime<\/code>, <code>overtime<\/code> and <code>result<\/code> are running totals.<\/p>\n<h3>How do I detect a shootout from the API?<\/h3>\n<p>Diff <code>result<\/code> against <code>overtime<\/code> in the <code>\/v4\/scores<\/code> periods object. If they match, a goal in the extra period settled it. If <code>overtime<\/code> still equals <code>fulltime<\/code> while <code>result<\/code> differs, the shootout decided it. Twelve of 63 April fixtures went past regulation, split seven and five.<\/p>\n<h3>What does a first-period market cost in margin?<\/h3>\n<p>On the opening-night board, First Period Result ran a 9.68% median margin across 57 complete quotes against 7.72% on Regular Time Result and 5.49% on the two-way Winner. Inside the first-period board the spread is wide: <code>caesars<\/code> quoted First Period Total 1.5 at 3.95% against a board median of 8.82%.<\/p>\n<h3>Is the NHL period board available on the free tier?<\/h3>\n<p>Yes. Every market ID on a fixture comes back in one <code>\/v4\/odds<\/code> call, period markets included, and the free tier allows 250 requests a month with no card. Historical odds and settlements are on the same key.<\/p>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\"@type\":\"Question\",\"name\":\"How do I tell a first-period market from a full-game market?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Read the period field on \/v4\/markets. It returns p1, p2 or p3 for a single period, fulltime for the 60-minute score and result for the final score. The market name will not tell you, because First Period Total and Total both read as a totals market and can carry the same handicap.\"}},\n    {\"@type\":\"Question\",\"name\":\"How many bookmakers price NHL period markets?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"On Carolina v Florida, measured 18 September 2026 at T-11d, 95 of the 225 bookmakers on the fixture quoted at least one first-period market and 55 quoted all three periods. Forty quoted the first period and no other, including bovada.lv, hardrockbet and bet99.\"}},\n    {\"@type\":\"Question\",\"name\":\"Do the period scores add up to the full-time score?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. Across 63 played NHL fixtures from 5 to 12 April 2026, p1 plus p2 plus p3 equalled the fulltime line on all 63. The period keys are deltas; fulltime, overtime and result are running totals.\"}},\n    {\"@type\":\"Question\",\"name\":\"How do I detect a shootout from the API?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Diff result against overtime in the \/v4\/scores periods object. If they match, a goal in the extra period settled it. If overtime still equals fulltime while result differs, the shootout decided it. Twelve of 63 April fixtures went past regulation, split seven and five.\"}},\n    {\"@type\":\"Question\",\"name\":\"What does a first-period market cost in margin?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"On the opening-night board, First Period Result ran a 9.68% median margin across 57 complete quotes against 7.72% on Regular Time Result and 5.49% on the two-way Winner. Inside the first-period board caesars quoted First Period Total 1.5 at 3.95% against a board median of 8.82%.\"}},\n    {\"@type\":\"Question\",\"name\":\"Is the NHL period board available on the free tier?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. Every market ID on a fixture comes back in one \/v4\/odds call, period markets included, and the free tier allows 250 requests a month with no card. Historical odds and settlements are on the same key.\"}}\n  ]\n}\n<\/script><\/p>\n<p><!--\nSEO\nFocus keyphrase: nhl odds api\nTitle: NHL Odds API: 95 Books Price the First Period Before Puck Drop\nMeta description: Carolina v Florida opens 225 books deep, and 95 of its 270 market IDs settle on one period. Those first-period rungs read 93.6% live eleven days out.\nSlug: nhl-first-period-odds-api\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Carolina v Florida opens 225 books deep, and 95 of its 270 market IDs settle on one period. Those first-period rungs read 93.6% live eleven days out.<\/p>\n","protected":false},"author":2,"featured_media":4261,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[100,58,9,11,10],"class_list":["post-4260","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-ice-hockey","tag-nhl","tag-odds-api","tag-python","tag-sports-betting-api"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>NHL Odds API: 95 Books Price the First Period Before Puck Drop | OddsPapi 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-first-period-odds-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"NHL Odds API: 95 Books Price the First Period Before Puck Drop | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"Carolina v Florida opens 225 books deep, and 95 of its 270 market IDs settle on one period. Those first-period rungs read 93.6% live eleven days out.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-29T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"675\" \/>\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=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"NHL Odds API: 95 Books Price the First Period Before Puck Drop\",\"datePublished\":\"2026-09-29T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\"},\"wordCount\":1724,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp\",\"keywords\":[\"Ice Hockey\",\"NHL\",\"Odds API\",\"Python\",\"Sports Betting API\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\",\"name\":\"NHL Odds API: 95 Books Price the First Period Before Puck Drop | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp\",\"datePublished\":\"2026-09-29T10:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp\",\"width\":1200,\"height\":675,\"caption\":\"NHL Odds API - first period markets from 95 bookmakers - OddsPapi\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"NHL Odds API: 95 Books Price the First Period Before Puck Drop\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"name\":\"OddsPapi\",\"description\":\"Sports Odds API 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: 95 Books Price the First Period Before Puck Drop | OddsPapi 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-first-period-odds-api\/","og_locale":"en_US","og_type":"article","og_title":"NHL Odds API: 95 Books Price the First Period Before Puck Drop | OddsPapi Blog","og_description":"Carolina v Florida opens 225 books deep, and 95 of its 270 market IDs settle on one period. Those first-period rungs read 93.6% live eleven days out.","og_url":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-09-29T10:00:00+00:00","og_image":[{"width":1200,"height":675,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.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":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"NHL Odds API: 95 Books Price the First Period Before Puck Drop","datePublished":"2026-09-29T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/"},"wordCount":1724,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp","keywords":["Ice Hockey","NHL","Odds API","Python","Sports Betting API"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/","url":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/","name":"NHL Odds API: 95 Books Price the First Period Before Puck Drop | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp","datePublished":"2026-09-29T10:00:00+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/nhl-first-period-odds-api.webp","width":1200,"height":675,"caption":"NHL Odds API - first period markets from 95 bookmakers - OddsPapi"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/nhl-first-period-odds-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"NHL Odds API: 95 Books Price the First Period Before Puck Drop"}]},{"@type":"WebSite","@id":"https:\/\/oddspapi.io\/blog\/#website","url":"https:\/\/oddspapi.io\/blog\/","name":"OddsPapi","description":"Sports Odds API 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\/4260","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=4260"}],"version-history":[{"count":1,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4260\/revisions"}],"predecessor-version":[{"id":4262,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4260\/revisions\/4262"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/4261"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=4260"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=4260"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=4260"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}