{"id":4445,"date":"2026-10-06T10:00:00","date_gmt":"2026-10-06T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=4445"},"modified":"2026-09-24T13:22:52","modified_gmt":"2026-09-24T13:22:52","slug":"tennis-moneyline-odds-api","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/","title":{"rendered":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match"},"content":{"rendered":"<p>Looking for a tennis moneyline odds API that covers every level, from the ATP and WTA tours down to ITF and UTR, with pregame prices? OddsPapi returns the match winner for all of them from one endpoint. Call <code>\/v4\/odds<\/code> with a fixture ID and read market <code>121<\/code>, the Winner. Every quote carries the decimal price and the American moneyline side by side, so a US-facing app needs no conversion step.<\/p>\n<p>We measured the depth on 24 September 2026 across 76 pregame singles boards in eight tiers. The median ITF match drew <strong>174 books<\/strong> on the moneyline. Challenger matches drew 237 and WTA Tour matches 239.5. The price gets wider as the tier drops: the median margin runs from 6.09% on the WTA Tour to 8.97% at ITF, and Pinnacle&#8217;s closing limit drops from 875 to 225. This guide shows how to find each tier, pull the moneyline, and pick a fair-value anchor that holds at every level.<\/p>\n<h2>Why an all-levels tennis feed is harder than it looks<\/h2>\n<p>Most tennis matches happen below the main tour. On the morning we measured, the API listed 203 pregame tennis matches with prices for the next three days. The ATP and WTA Tours held 38 of them. Challenger, WTA 125, ITF and UTR held 151. If your feed or your filter stops at the tour, you see about one match in five.<\/p>\n<p>The lower tiers also break code that works fine on the ATP. Three things catch developers:<\/p>\n<ul>\n<li><strong>ITF fixtures use a second ID scheme.<\/strong> Their IDs start with <code>pn<\/code> instead of <code>id<\/code>, and they ship with <code>statusName<\/code> and <code>statusId<\/code> set to null. A pregame filter on <code>statusName == \"Pre-Game\"<\/code> drops every ITF match without an error.<\/li>\n<li><strong>The participant name order changes.<\/strong> ITF fixtures write &#8220;Astra Sharma&#8221;. The <code>id<\/code> scheme writes &#8220;Yamamoto, Natsuki&#8221;. Join on participant IDs, never on names.<\/li>\n<li><strong>The sharp anchor loosens.<\/strong> Pinnacle&#8217;s margin and stake limit change by tier, so a fair-value model tuned on the tour misprices an ITF match.<\/li>\n<\/ul>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>What an all-levels moneyline needs<\/th>\n<th>What the v4 feed ships (measured 24 Sep 2026)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Every tier in one call pattern<\/td>\n<td>12 tennis categories from <code>\/v4\/fixtures?sportId=12<\/code>, ATP to UTR<\/td>\n<\/tr>\n<tr>\n<td>Depth below the tour<\/td>\n<td>Median 174 books on an ITF singles moneyline, 237 on Challenger<\/td>\n<\/tr>\n<tr>\n<td>American odds<\/td>\n<td><code>priceAmerican<\/code> on every quote; it matched the decimal price on 22,783 of 22,783 legs<\/td>\n<\/tr>\n<tr>\n<td>A sharp reference<\/td>\n<td>Pinnacle, with a stake <code>limit<\/code> on each price<\/td>\n<\/tr>\n<tr>\n<td>Prediction-market prices<\/td>\n<td><code>kalshi<\/code> and <code>polymarket<\/code> on 9 or 10 of 10 Challenger and ITF boards<\/td>\n<\/tr>\n<tr>\n<td>Closing lines for backtests<\/td>\n<td><code>\/v4\/historical-odds<\/code> on played matches, ITF included, on the free tier<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: list the tennis tiers<\/h2>\n<p>Tennis is sport ID 12. <code>\/v4\/fixtures<\/code> returns every match in the window, and <code>categoryName<\/code> carries the tier. The key goes in the query string, never in a header. Define &#8220;pregame&#8221; as a start time that is still ahead, because the ITF records carry no status.<\/p>\n<pre class=\"wp-block-code\"><code>import os, time, requests\nfrom collections import Counter\nfrom datetime import datetime, timedelta, timezone\n\nAPI_KEY = os.environ[&quot;ODDSPAPI_API_KEY&quot;]\nBASE = &quot;https:\/\/api.oddspapi.io\/v4&quot;\n\ndef get(path, **params):\n    params[&quot;apiKey&quot;] = API_KEY\n    for _ in range(4):\n        r = requests.get(f&quot;{BASE}\/{path}&quot;, params=params, timeout=120)\n        if r.status_code == 429:\n            time.sleep(r.json().get(&quot;error&quot;, {}).get(&quot;retryMs&quot;, 2000) \/ 1000 + 0.5)\n            continue\n        r.raise_for_status()\n        return r.json()\n    raise RuntimeError(&quot;rate limited&quot;)\n\nnow = datetime.now(timezone.utc)\nfixtures = get(&quot;fixtures&quot;, sportId=12,\n               **{&quot;from&quot;: now.strftime(&quot;%Y-%m-%d&quot;),\n                  &quot;to&quot;: (now + timedelta(days=3)).strftime(&quot;%Y-%m-%d&quot;)})\n\ndef starts(f):\n    return datetime.fromisoformat(f[&quot;startTime&quot;].replace(&quot;Z&quot;, &quot;+00:00&quot;))\n\n# Pregame = the start time is still ahead. Do not filter on statusName:\n# ITF fixtures ship with statusName and statusId set to null.\npregame = [f for f in fixtures if f[&quot;hasOdds&quot;] and starts(f) &gt; now]\n\ntiers = Counter(f[&quot;categoryName&quot;] for f in pregame)\nfor tier, n in tiers.most_common():\n    scheme = Counter(f[&quot;fixtureId&quot;][:2] for f in pregame if f[&quot;categoryName&quot;] == tier)\n    print(f&quot;{tier:&lt;26}{n:&gt;4}  id prefix: {dict(scheme)}&quot;)<\/code><\/pre>\n<p>Output on 24 September 2026:<\/p>\n<pre class=\"wp-block-code\"><code>UTR Men                     39  id prefix: {&#x27;id&#x27;: 39}\nChallenger                  33  id prefix: {&#x27;id&#x27;: 33}\nITF Women                   27  id prefix: {&#x27;pn&#x27;: 27}\nATP                         27  id prefix: {&#x27;id&#x27;: 27}\nUTR Women                   19  id prefix: {&#x27;id&#x27;: 19}\nWTA 125K                    17  id prefix: {&#x27;id&#x27;: 17}\nITF Men                     16  id prefix: {&#x27;pn&#x27;: 16}\nWTA                         11  id prefix: {&#x27;id&#x27;: 11}\nSimulated Reality Women      4  id prefix: {&#x27;id&#x27;: 4}\nSimulated Reality            4  id prefix: {&#x27;id&#x27;: 4}\nExhibition                   4  id prefix: {&#x27;id&#x27;: 4}\nBillie Jean King Cup         2  id prefix: {&#x27;id&#x27;: 2}<\/code><\/pre>\n<p>Two rows need a decision. <code>Simulated Reality<\/code> and <code>Simulated Reality Women<\/code> are virtual matches, so drop them from any model of real players. <code>Exhibition<\/code> and <code>Billie Jean King Cup<\/code> are real, but they run on their own calendar. Every other row is a tier you can price.<\/p>\n<h2>Step 2: pull the moneyline for one match<\/h2>\n<p>The snippet below takes the next six ITF matches, keeps the deepest board, and reads market <code>121<\/code>. Outcome <code>121<\/code> is participant 1 and outcome <code>122<\/code> is participant 2. The catalogue names them &#8220;1&#8221; and &#8220;2&#8221;, so join back to the fixture for the player names.<\/p>\n<p>Each quote passes four checks before it counts. The book is not suspended. Both legs carry <code>active: true<\/code>. Both prices sit above 1.0. The quote changed within six hours of the newest price that book posted on the same fixture, which catches a withdrawn market that still reads as active.<\/p>\n<pre class=\"wp-block-code\"><code>import os, time, statistics, requests\nfrom datetime import datetime, timedelta, timezone\n\nAPI_KEY = os.environ[&quot;ODDSPAPI_API_KEY&quot;]\nBASE = &quot;https:\/\/api.oddspapi.io\/v4&quot;\n\ndef get(path, **params):\n    params[&quot;apiKey&quot;] = API_KEY\n    for _ in range(4):\n        r = requests.get(f&quot;{BASE}\/{path}&quot;, params=params, timeout=120)\n        if r.status_code == 429:\n            time.sleep(r.json().get(&quot;error&quot;, {}).get(&quot;retryMs&quot;, 2000) \/ 1000 + 0.5)\n            continue\n        r.raise_for_status()\n        return r.json()\n    raise RuntimeError(&quot;rate limited&quot;)\n\ndef ts(s):\n    return datetime.fromisoformat(s.replace(&quot;Z&quot;, &quot;+00:00&quot;))\n\nnow = datetime.now(timezone.utc)\nfixtures = get(&quot;fixtures&quot;, sportId=12,\n               **{&quot;from&quot;: now.strftime(&quot;%Y-%m-%d&quot;),\n                  &quot;to&quot;: (now + timedelta(days=2)).strftime(&quot;%Y-%m-%d&quot;)})\nitf = sorted((f for f in fixtures\n              if f[&quot;categoryName&quot;].startswith(&quot;ITF&quot;) and f[&quot;hasOdds&quot;]\n              and ts(f[&quot;startTime&quot;]) &gt; now + timedelta(hours=1)),\n             key=lambda f: ts(f[&quot;startTime&quot;]))\nWINNER, P1, P2 = &quot;121&quot;, &quot;121&quot;, &quot;122&quot;   # market 121 = Winner (moneyline)\n\ndef moneylines(board):\n    quotes = {}\n    for slug, book in board.items():\n        if &quot;+&quot; in slug or slug == &quot;demo&quot;:       # internal test feeds\n            continue\n        mkt = book.get(&quot;markets&quot;, {}).get(WINNER)\n        if not mkt:\n            continue\n        try:\n            a = mkt[&quot;outcomes&quot;][P1][&quot;players&quot;][&quot;0&quot;]\n            b = mkt[&quot;outcomes&quot;][P2][&quot;players&quot;][&quot;0&quot;]\n        except KeyError:\n            continue\n        newest = max(p[&quot;changedAt&quot;] for m in book[&quot;markets&quot;].values()\n                     for o in m[&quot;outcomes&quot;].values() for p in o[&quot;players&quot;].values())\n        fresh = ts(newest) - ts(max(a[&quot;changedAt&quot;], b[&quot;changedAt&quot;])) &lt; timedelta(hours=6)\n        live = (not book.get(&quot;suspended&quot;, False) and book.get(&quot;bookmakerIsActive&quot;, True)\n                and a[&quot;active&quot;] and b[&quot;active&quot;] and a[&quot;price&quot;] &gt; 1 and b[&quot;price&quot;] &gt; 1)\n        if live and fresh and 1 \/ a[&quot;price&quot;] + 1 \/ b[&quot;price&quot;] &gt; 1:\n            quotes[slug] = (a[&quot;price&quot;], b[&quot;price&quot;], a[&quot;priceAmerican&quot;], b[&quot;priceAmerican&quot;],\n                            a[&quot;limit&quot;], b[&quot;limit&quot;])\n    return quotes\n\nbest = None\nfor fx in itf[:6]:                      # keep the deepest of the next six ITF boards\n    time.sleep(1.0)\n    board = get(&quot;odds&quot;, fixtureId=fx[&quot;fixtureId&quot;])[&quot;bookmakerOdds&quot;]\n    quotes = moneylines(board)\n    if best is None or len(quotes) &gt; len(best[2]):\n        best = (fx, board, quotes)\nfx, board, quotes = best\nprint(fx[&quot;tournamentName&quot;], &quot;|&quot;, fx[&quot;participant1Name&quot;], &quot;v&quot;, fx[&quot;participant2Name&quot;],\n      &quot;|&quot;, fx[&quot;startTime&quot;])\n\nmargin = lambda q: (1 \/ q[0] + 1 \/ q[1] - 1) * 100\ndistinct = {(q[0], q[1]) for q in quotes.values()}\nprint(f&quot;books on the fixture: {len(board)}, tradeable moneylines: {len(quotes)}, &quot;\n      f&quot;distinct price pairs: {len(distinct)}&quot;)\nprint(f&quot;median margin: {statistics.median(margin(q) for q in quotes.values()):.2f}%&quot;)\n\nfor slug in (&quot;pinnacle&quot;, &quot;bet365&quot;, &quot;draftkings&quot;, &quot;kalshi&quot;, &quot;polymarket&quot;):\n    if slug in quotes:\n        q = quotes[slug]\n        lim = f&quot;  limit {min(q[4], q[5]):,.2f}&quot; if q[4] and q[5] else &quot;&quot;\n        print(f&quot;{slug:&lt;11}{q[0]:&gt;7}{q[1]:&gt;7}   {q[2]:&gt;5} \/ {q[3]:&lt;5} {margin(q):5.2f}%{lim}&quot;)<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>Columbia | John Hallquist Lithen v Charlie Swaine | 2026-09-24T15:00:00.000Z\nbooks on the fixture: 188, tradeable moneylines: 166, distinct price pairs: 33\nmedian margin: 8.84%\nbet365       1.068    7.0   -1471 \/ 600    7.92%\ndraftkings   1.071    7.0   -1408 \/ 600    7.66%\nkalshi       1.099  8.333   -1010 \/ 733    2.99%  limit 124.45\npolymarket   1.124  7.143    -806 \/ 614    2.97%  limit 2.80<\/code><\/pre>\n<p>The <code>priceAmerican<\/code> field is a string. It was blank on 47 of 22,830 legs in our census, and each of those 47 had a decimal price of exactly 1.0, a placeholder. The <code>price &gt; 1<\/code> check removes them. If you need fractional odds, <code>priceFractional<\/code> ships next to it; <a href=\"https:\/\/oddspapi.io\/blog\/polymarket-odds-converter-python\/\">the odds converter guide<\/a> covers the arithmetic if you want to derive them yourself.<\/p>\n<p>Look at the gap between 166 tradeable quotes and 33 distinct price pairs. Many slugs are regional skins of one trading desk and post byte-identical prices. Dedupe on the price pair before you average anything, or one desk votes thirty times.<\/p>\n<h2>Step 3: measure every tier<\/h2>\n<p>This loop reuses <code>moneylines()<\/code> from Step 2 and samples the next three singles matches in each tier. Doubles boards are thinner and follow a different pattern, so the filter drops them.<\/p>\n<pre class=\"wp-block-code\"><code># Reuses get(), ts(), fixtures and moneylines() from the step above.\nfrom collections import defaultdict\n\nTIERS = [&quot;ATP&quot;, &quot;WTA&quot;, &quot;Challenger&quot;, &quot;WTA 125K&quot;, &quot;ITF Men&quot;, &quot;ITF Women&quot;,\n         &quot;UTR Men&quot;, &quot;UTR Women&quot;]\n\ndef singles(f):\n    return &quot;Doubles&quot; not in f[&quot;tournamentName&quot;] and &quot;\/&quot; not in f.get(&quot;participant1Name&quot;, &quot;\/&quot;)\n\nrows = defaultdict(list)\nfor tier in TIERS:\n    picks = sorted((f for f in fixtures if f[&quot;categoryName&quot;] == tier and f[&quot;hasOdds&quot;]\n                    and singles(f) and ts(f[&quot;startTime&quot;]) &gt; now + timedelta(minutes=45)),\n                   key=lambda f: ts(f[&quot;startTime&quot;]))[:3]\n    for fx in picks:\n        time.sleep(1.0)\n        quotes = moneylines(get(&quot;odds&quot;, fixtureId=fx[&quot;fixtureId&quot;])[&quot;bookmakerOdds&quot;])\n        if not quotes:\n            continue\n        pin = quotes.get(&quot;pinnacle&quot;)\n        rows[tier].append((len(quotes),\n                           len({(q[0], q[1]) for q in quotes.values()}),\n                           statistics.median(margin(q) for q in quotes.values()),\n                           margin(pin) if pin else None,\n                           min(pin[4], pin[5]) if pin and pin[4] and pin[5] else None))\n\nmed = lambda xs: statistics.median(xs) if xs else None\nprint(f&quot;{&#x27;tier&#x27;:&lt;11}{&#x27;boards&#x27;:&gt;7}{&#x27;books&#x27;:&gt;7}{&#x27;pairs&#x27;:&gt;7}{&#x27;margin&#x27;:&gt;8}{&#x27;pinnacle&#x27;:&gt;10}{&#x27;pin limit&#x27;:&gt;11}&quot;)\nfor tier, r in rows.items():\n    pm = med([x[3] for x in r if x[3] is not None])\n    pl = med([x[4] for x in r if x[4] is not None])\n    print(f&quot;{tier:&lt;11}{len(r):&gt;7}{med([x[0] for x in r]):&gt;7}{med([x[1] for x in r]):&gt;7}&quot;\n          f&quot;{med([x[2] for x in r]):&gt;7.2f}%&quot;\n          + (f&quot;{pm:&gt;9.2f}%&quot; if pm is not None else f&quot;{&#x27;-&#x27;:&gt;10}&quot;)\n          + (f&quot;{pl:&gt;11,.0f}&quot; if pl is not None else f&quot;{&#x27;-&#x27;:&gt;11}&quot;))<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>tier        boards  books  pairs  margin  pinnacle  pin limit\nATP              3    196     49   6.36%     3.29%        162\nWTA              3    212     53   5.81%     2.53%      3,000\nChallenger       3    231     54   7.91%     4.66%        572\nWTA 125K         3    178     37   7.60%     4.18%        875\nITF Men          3    128     26   9.01%     2.67%        225\nITF Women        3    141     33   9.04%     4.86%        200\nUTR Men          3    106     25   8.90%         -          -\nUTR Women        3     79     19   8.29%         -          -<\/code><\/pre>\n<p>A three-board sample moves around from run to run. For the numbers in this post we ran the same logic on 76 singles boards between 13:11 and 13:14 UTC on 24 September, ten per tier where ten existed:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Tier<\/th>\n<th>Boards<\/th>\n<th>Hours to start<\/th>\n<th>Books on the moneyline (median)<\/th>\n<th>Tradeable quotes<\/th>\n<th>Duplicate quotes<\/th>\n<th>Median margin<\/th>\n<th>Pinnacle margin<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>WTA<\/td>\n<td>6<\/td>\n<td>13.8 to 17.3<\/td>\n<td>239.5<\/td>\n<td>235<\/td>\n<td>76.5%<\/td>\n<td>6.09%<\/td>\n<td>3.12%<\/td>\n<\/tr>\n<tr>\n<td>ATP<\/td>\n<td>10<\/td>\n<td>15.8 to 37.8<\/td>\n<td>163<\/td>\n<td>163<\/td>\n<td>74.8%<\/td>\n<td>6.61%<\/td>\n<td>3.40%<\/td>\n<\/tr>\n<tr>\n<td>Challenger<\/td>\n<td>10<\/td>\n<td>0.8 to 7.0<\/td>\n<td>237<\/td>\n<td>231<\/td>\n<td>77.8%<\/td>\n<td>7.93%<\/td>\n<td>4.50%<\/td>\n<\/tr>\n<tr>\n<td>WTA 125<\/td>\n<td>10<\/td>\n<td>0.8 to 20.8<\/td>\n<td>216<\/td>\n<td>210<\/td>\n<td>77.0%<\/td>\n<td>7.76%<\/td>\n<td>4.31%<\/td>\n<\/tr>\n<tr>\n<td>ITF Men<\/td>\n<td>10<\/td>\n<td>0.8 to 12.8<\/td>\n<td>174<\/td>\n<td>163<\/td>\n<td>78.3%<\/td>\n<td>8.97%<\/td>\n<td>4.72%<\/td>\n<\/tr>\n<tr>\n<td>ITF Women<\/td>\n<td>10<\/td>\n<td>1.8 to 12.8<\/td>\n<td>170<\/td>\n<td>151.5<\/td>\n<td>77.8%<\/td>\n<td>8.93%<\/td>\n<td>5.55%<\/td>\n<\/tr>\n<tr>\n<td>UTR Men<\/td>\n<td>10<\/td>\n<td>0.8 to 17.8<\/td>\n<td>93.5<\/td>\n<td>92.5<\/td>\n<td>77.1%<\/td>\n<td>8.90%<\/td>\n<td>n\/a<\/td>\n<\/tr>\n<tr>\n<td>UTR Women<\/td>\n<td>10<\/td>\n<td>0.8 to 24.3<\/td>\n<td>27.5<\/td>\n<td>24<\/td>\n<td>70.8%<\/td>\n<td>8.65%<\/td>\n<td>n\/a<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>&#8220;Duplicate quotes&#8221; is the share of tradeable quotes that repeat another book&#8217;s exact price pair. &#8220;Pinnacle margin&#8221; is the median over the boards where Pinnacle posted a tradeable price: 5 of 10 ATP, 6 of 6 WTA, 10 of 10 Challenger, 2 of 10 WTA 125, 6 of 10 ITF Men and 7 of 10 ITF Women.<\/p>\n<p><strong>The board fills in the last day.<\/strong> The ATP median of 163 is low because every ATP match we sampled was 16 to 38 hours away. One Chengdu match 37.8 hours out carried 4 books; one Hangzhou match 16.3 hours out carried 250. The UTR boards more than 17 hours out held a single <code>polymarket.us<\/code> quote, while the UTR boards inside 12 hours held a median of 82 books. Poll a match again in its final 12 hours before you judge its depth.<\/p>\n<p><strong>ITF depth is real depth.<\/strong> A median of 174 books on an ITF singles match beats the 163 we measured on ATP boards 16 or more hours out. <code>bet365<\/code> quoted all 10 ITF Men boards, <code>polymarket<\/code> quoted all 20 ITF boards, and <code>kalshi<\/code> quoted 18.<\/p>\n<p><strong>The margin climbs about three points from the WTA Tour to ITF.<\/strong> The median book charges 6.09% on a WTA match and 8.97% on an ITF Men match. Line shopping matters more at the bottom of the sport. <a href=\"https:\/\/oddspapi.io\/blog\/line-shopping-python-best-odds\/\">The line shopping guide<\/a> has the best-price code.<\/p>\n<p><strong>About three quotes in four repeat another book&#8217;s price.<\/strong> The collapse holds at 71% to 78% on every tier, so a raw book count overstates the number of independent opinions by about four times.<\/p>\n<h2>Step 4: pick the fair-value anchor by tier<\/h2>\n<p>Pinnacle is the usual reference price. In tennis, its margin widens and its stake limit shrinks as the tier drops. The live boards showed that, and the closing prices on played matches confirm it. This snippet reads the closing moneyline on three ITF matches played the day before, from <code>\/v4\/historical-odds<\/code>, for three books at once:<\/p>\n<pre class=\"wp-block-code\"><code># Reuses get() and ts() from above. Closing moneyline on ITF matches played yesterday.\nyday = now - timedelta(days=1)\nplayed = get(&quot;fixtures&quot;, sportId=12, **{&quot;from&quot;: yday.strftime(&quot;%Y-%m-%d&quot;),\n                                        &quot;to&quot;: now.strftime(&quot;%Y-%m-%d&quot;)})\nitf_played = [f for f in played if f[&quot;categoryName&quot;].startswith(&quot;ITF&quot;)\n              and &quot;\/&quot; not in f.get(&quot;participant1Name&quot;, &quot;\/&quot;)\n              and ts(f[&quot;startTime&quot;]) &lt; now - timedelta(hours=6)][:3]\n\ndef close(snaps, start):\n    pre = [s for s in snaps if ts(s[&quot;createdAt&quot;]) &lt; start and s[&quot;active&quot;] and s[&quot;price&quot;] &gt; 1]\n    return max(pre, key=lambda s: s[&quot;createdAt&quot;]) if pre else None\n\nfor fx in itf_played:\n    time.sleep(4.6)                       # \/historical-odds needs a longer cooldown\n    hist = get(&quot;historical-odds&quot;, fixtureId=fx[&quot;fixtureId&quot;],\n               bookmakers=&quot;pinnacle,bet365,draftkings&quot;)[&quot;bookmakers&quot;]\n    start = ts(fx[&quot;startTime&quot;])\n    print(fx[&quot;tournamentName&quot;], &quot;|&quot;, fx[&quot;participant1Name&quot;], &quot;v&quot;, fx[&quot;participant2Name&quot;])\n    for slug, book in hist.items():\n        outs = book.get(&quot;markets&quot;, {}).get(&quot;121&quot;, {}).get(&quot;outcomes&quot;, {})\n        s1 = outs.get(&quot;121&quot;, {}).get(&quot;players&quot;, {}).get(&quot;0&quot;, [])\n        s2 = outs.get(&quot;122&quot;, {}).get(&quot;players&quot;, {}).get(&quot;0&quot;, [])\n        c1, c2 = close(s1, start), close(s2, start)\n        if not (c1 and c2):\n            continue\n        first = min(ts(s[&quot;createdAt&quot;]) for s in s1 + s2)\n        lim = f&quot;  limit {min(c1[&#x27;limit&#x27;], c2[&#x27;limit&#x27;]):,.0f}&quot; if c1[&quot;limit&quot;] and c2[&quot;limit&quot;] else &quot;&quot;\n        print(f&quot;  {slug:&lt;11} opened T-{(start - first).total_seconds() \/ 3600:4.1f}h  &quot;\n              f&quot;closed {c1[&#x27;price&#x27;]:.3f} \/ {c2[&#x27;price&#x27;]:.3f}  &quot;\n              f&quot;{(1 \/ c1[&#x27;price&#x27;] + 1 \/ c2[&#x27;price&#x27;] - 1) * 100:4.2f}%{lim}&quot;)<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>Nanao | Haruna Arakawa v Hiromi Abe\n  bet365      opened T-28.6h  closed 7.000 \/ 1.068  7.92%\n  draftkings  opened T-11.6h  closed 7.000 \/ 1.071  7.66%\n  pinnacle    opened T-27.9h  closed 6.390 \/ 1.125  4.54%  limit 225\nNanao | Kisa Yoshioka v Hikaru Sato\n  bet365      opened T-28.6h  closed 2.370 \/ 1.500  8.86%\n  draftkings  opened T-11.6h  closed 2.250 \/ 1.541  9.34%\n  pinnacle    opened T-27.9h  closed 2.330 \/ 1.617  4.76%  limit 225\nNanao | Jiangxue Han v Kurumi Tamura\n  bet365      opened T- 2.8h  closed 1.270 \/ 3.400  8.15%\n  draftkings  opened T-10.8h  closed 1.250 \/ 3.650  7.40%\n  pinnacle    opened T-12.4h  closed 1.289 \/ 3.670  4.83%  limit 225<\/code><\/pre>\n<p>The closing price is the last active snapshot before the start time. Snapshots keep recording in-play, so the <code>createdAt &lt; start<\/code> filter matters. We ran the same close on five played singles matches per tier from 22 and 23 September:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Tier (5 matches each)<\/th>\n<th>Pinnacle close margin<\/th>\n<th>Pinnacle close limit, smaller side<\/th>\n<th>bet365 close margin<\/th>\n<th>DraftKings close margin<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ATP and WTA<\/td>\n<td>3.74%<\/td>\n<td>875<\/td>\n<td>6.79%<\/td>\n<td>4.76%<\/td>\n<\/tr>\n<tr>\n<td>Challenger<\/td>\n<td>4.31%<\/td>\n<td>875<\/td>\n<td>8.75%<\/td>\n<td>6.48%<\/td>\n<\/tr>\n<tr>\n<td>WTA 125<\/td>\n<td>4.43%<\/td>\n<td>875<\/td>\n<td>7.14%<\/td>\n<td>6.53%<\/td>\n<\/tr>\n<tr>\n<td>ITF<\/td>\n<td>4.84%<\/td>\n<td>225<\/td>\n<td>8.57%<\/td>\n<td>7.61%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>All values are medians. Pinnacle keeps the tightest close of the three books on every tier, but at ITF it takes about a quarter of the stake it takes on a Challenger match. A smaller limit means Pinnacle absorbs less money at its own number. Use two rules:<\/p>\n<ul>\n<li><strong>ATP, WTA, Challenger and WTA 125:<\/strong> Pinnacle&#8217;s de-vigged price is a sound anchor. <a href=\"https:\/\/oddspapi.io\/blog\/no-vig-odds-api\/\">The no-vig guide<\/a> shows three ways to strip the margin.<\/li>\n<li><strong>ITF and UTR:<\/strong> de-vig the deduplicated consensus instead. With 30 to 35 distinct price pairs on a typical ITF board, the consensus carries more information than one book at a 225 limit. Pinnacle did not quote a UTR match in our sample, so the consensus is the only anchor there. <a href=\"https:\/\/oddspapi.io\/blog\/consensus-odds-fair-odds-calculator-python\/\">The consensus odds calculator<\/a> builds that number.<\/li>\n<\/ul>\n<h2>What to build with it<\/h2>\n<ul>\n<li><strong>A lower-tier value scanner.<\/strong> Run Step 3 every 15 minutes on matches inside 12 hours and flag any book priced above the deduplicated consensus. The three-point margin gap means soft prices show up more often at ITF than on the tour.<\/li>\n<li><strong>A tennis rating model.<\/strong> <a href=\"https:\/\/oddspapi.io\/blog\/tennis-elo-model-python\/\">The tennis Elo guide<\/a> trains against closing odds. Step 4 gives you closes for ITF and Challenger players, who have far more matches than tour players.<\/li>\n<li><strong>A Grand Slam pipeline.<\/strong> <a href=\"https:\/\/oddspapi.io\/blog\/us-open-odds-api\/\">The US Open study<\/a> covers the top of the sport, where the board reached 155 books a match in the first round.<\/li>\n<\/ul>\n<p>For the full tennis market menu (game handicaps, set totals, first-set winner), start from <a href=\"https:\/\/oddspapi.io\/blog\/tennis-odds-api-live-atp-wta\/\">the tennis odds API guide<\/a>.<\/p>\n<h2>Tennis moneyline odds API FAQ<\/h2>\n<h3>Is there an odds API for tennis at all levels with pregame moneylines?<\/h3>\n<p>Yes. OddsPapi&#8217;s <code>\/v4\/odds<\/code> returns the pregame match winner (market 121) for ATP, WTA, Challenger, WTA 125, ITF and UTR matches. On 24 September 2026 the median ITF singles match drew 174 books on that market.<\/p>\n<h3>Which market ID is the tennis moneyline?<\/h3>\n<p>Market 121, named &#8220;Winner&#8221; in <code>\/v4\/markets<\/code>, with outcome 121 for participant 1 and outcome 122 for participant 2. The set winners use separate IDs: 123 for the first set and 125 for the second.<\/p>\n<h3>Does the API return American odds for tennis?<\/h3>\n<p>Yes. Every price carries <code>priceAmerican<\/code> as a string next to the decimal <code>price<\/code>, plus <code>priceFractional<\/code>. In our census the American value matched the decimal price on 22,783 of 22,783 legs.<\/p>\n<h3>How do I tell an ITF match from an ATP match in the API?<\/h3>\n<p>Read <code>categoryName<\/code> on <code>\/v4\/fixtures<\/code>: &#8220;ITF Men&#8221;, &#8220;ITF Women&#8221;, &#8220;Challenger&#8221;, &#8220;ATP&#8221;, &#8220;WTA&#8221;, &#8220;WTA 125K&#8221;, &#8220;UTR Men&#8221; and &#8220;UTR Women&#8221;. ITF fixture IDs also start with <code>pn<\/code>, and they ship without a status, so filter pregame matches on <code>startTime<\/code>.<\/p>\n<h3>Can I get historical tennis moneyline odds for ITF matches?<\/h3>\n<p>Yes. <code>\/v4\/historical-odds<\/code> returns the full price history for played ITF matches on the free tier, up to three bookmakers a call. Pinnacle&#8217;s median closing margin across five ITF matches from 22 and 23 September 2026 was 4.84%.<\/p>\n<h2>Pull your first ITF moneyline<\/h2>\n<p>Every call in this guide runs on the free key: 350+ bookmakers in the catalogue, free historical odds for your closing-line backtests, and the same fixture IDs on the <a href=\"https:\/\/oddspapi.io\/blog\/websocket-odds-api-real-time-betting-data\/\">WebSocket feed<\/a> when polling gets too slow for in-play. <a href=\"https:\/\/oddspapi.io\/en\/sign-up\">Get a free API key<\/a> and run Step 2 on the next ITF match on the schedule.<\/p>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is there an odds API for tennis at all levels with pregame moneylines?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. OddsPapi's \/v4\/odds returns the pregame match winner (market 121) for ATP, WTA, Challenger, WTA 125, ITF and UTR matches. On 24 September 2026 the median ITF singles match drew 174 books on that market.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Which market ID is the tennis moneyline?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Market 121, named \\\"Winner\\\" in \/v4\/markets, with outcome 121 for participant 1 and outcome 122 for participant 2. The set winners use separate IDs: 123 for the first set and 125 for the second.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Does the API return American odds for tennis?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. Every price carries priceAmerican as a string next to the decimal price, plus priceFractional. In our census the American value matched the decimal price on 22,783 of 22,783 legs.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How do I tell an ITF match from an ATP match in the API?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Read categoryName on \/v4\/fixtures: \\\"ITF Men\\\", \\\"ITF Women\\\", \\\"Challenger\\\", \\\"ATP\\\", \\\"WTA\\\", \\\"WTA 125K\\\", \\\"UTR Men\\\" and \\\"UTR Women\\\". ITF fixture IDs also start with pn, and they ship without a status, so filter pregame matches on startTime.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Can I get historical tennis moneyline odds for ITF matches?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. \/v4\/historical-odds returns the full price history for played ITF matches on the free tier, up to three bookmakers a call. Pinnacle's median closing margin across five ITF matches from 22 and 23 September 2026 was 4.84%.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<p><!--\nFocus Keyphrase: tennis moneyline odds api\nSEO Title: Tennis Moneyline Odds API: Pull 174 Books per ITF Match\nMeta Description: A tennis moneyline odds API from the WTA Tour down to ITF: market 121 ships American odds, and Pinnacle's closing limit drops from 875 to 225 at ITF level.\nSlug: tennis-moneyline-odds-api\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A tennis moneyline odds API from the WTA Tour down to ITF: market 121 ships American odds, and Pinnacle&#8217;s closing limit drops from 875 to 225 at ITF level.<\/p>\n","protected":false},"author":2,"featured_media":4446,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[4,9,11,25],"class_list":["post-4445","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-historical-odds","tag-odds-api","tag-python","tag-tennis"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Tennis Moneyline Odds API: Pull 174 Books per ITF Match | 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\/tennis-moneyline-odds-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tennis Moneyline Odds API: Pull 174 Books per ITF Match | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"A tennis moneyline odds API from the WTA Tour down to ITF: market 121 ships American odds, and Pinnacle&#039;s closing limit drops from 875 to 225 at ITF level.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-06T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-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=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"Tennis Moneyline Odds API: Pull 174 Books per ITF Match\",\"datePublished\":\"2026-10-06T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\"},\"wordCount\":1631,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp\",\"keywords\":[\"historical odds\",\"Odds API\",\"Python\",\"Tennis\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\",\"name\":\"Tennis Moneyline Odds API: Pull 174 Books per ITF Match | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp\",\"datePublished\":\"2026-10-06T10:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp\",\"width\":1200,\"height\":675,\"caption\":\"Tennis Moneyline Odds API\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tennis Moneyline Odds API: Pull 174 Books per ITF Match\"}]},{\"@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":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match | 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\/tennis-moneyline-odds-api\/","og_locale":"en_US","og_type":"article","og_title":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match | OddsPapi Blog","og_description":"A tennis moneyline odds API from the WTA Tour down to ITF: market 121 ships American odds, and Pinnacle's closing limit drops from 875 to 225 at ITF level.","og_url":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-10-06T10:00:00+00:00","og_image":[{"width":1200,"height":675,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-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":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match","datePublished":"2026-10-06T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/"},"wordCount":1631,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp","keywords":["historical odds","Odds API","Python","Tennis"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/","url":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/","name":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp","datePublished":"2026-10-06T10:00:00+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/tennis-moneyline-odds-api.webp","width":1200,"height":675,"caption":"Tennis Moneyline Odds API"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/tennis-moneyline-odds-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"Tennis Moneyline Odds API: Pull 174 Books per ITF Match"}]},{"@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\/4445","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=4445"}],"version-history":[{"count":1,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4445\/revisions"}],"predecessor-version":[{"id":4447,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4445\/revisions\/4447"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/4446"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=4445"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=4445"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=4445"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}