{"id":3669,"date":"2026-08-22T10:00:00","date_gmt":"2026-08-22T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=3669"},"modified":"2026-09-07T14:01:48","modified_gmt":"2026-09-07T14:01:48","slug":"serie-a-odds-api","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/","title":{"rendered":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14"},"content":{"rendered":"<p>You want Serie A prices. You pull 30 days of fixtures from the API, get 40 matches back, and 20 of them report <code>hasOdds: true<\/code>. Looks like a full board.<\/p>\n<p>Ten of those twenty are a placeholder. On the Aug 28&ndash;31 round, exactly two venues quote a price, Kalshi and Polymarket, and on 15 of the 20 three-ways they publish they put <strong>the same price on all three outcomes<\/strong>. Kalshi shows 1.250 \/ 1.250 \/ 1.250 on eight of ten fixtures, a 140% margin. Every one of those prices arrives flagged <code>active: true<\/code> with a real order ladder behind it.<\/p>\n<p>This guide pulls live Serie A odds from the OddsPapi <a href=\"https:\/\/oddspapi.io\/blog\/free-odds-api-350-bookmakers\/\">free odds API<\/a> in Python, and it ships the margin check that separates the 17-book board from the two-book mirage. Every number below came off the live feed on 18 August 2026, four days before the season opens.<\/p>\n<h2>What the Serie A board actually looks like<\/h2>\n<p>Serie A is <code>tournamentId<\/code> 23 in the soccer catalogue. Here is the state of the league across three rounds, measured the same afternoon:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Round<\/th>\n<th>Dates<\/th>\n<th>Fixtures<\/th>\n<th><code>hasOdds<\/code><\/th>\n<th>Books quoting<\/th>\n<th>Markets per fixture<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Matchday 1<\/td>\n<td>Aug 22&ndash;24<\/td>\n<td>10<\/td>\n<td>true<\/td>\n<td><strong>17<\/strong><\/td>\n<td>1,177&ndash;1,278<\/td>\n<\/tr>\n<tr>\n<td>Matchday 2<\/td>\n<td>Aug 28&ndash;31<\/td>\n<td>10<\/td>\n<td>true<\/td>\n<td><strong>2<\/strong><\/td>\n<td>61<\/td>\n<\/tr>\n<tr>\n<td>Matchday 3<\/td>\n<td>Sep 4&ndash;7<\/td>\n<td>10<\/td>\n<td>false<\/td>\n<td>0<\/td>\n<td>0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Two rounds carry the same flag and nothing else in common. Matchday 1 gives you Pinnacle, SBOBet, Bet365, DraftKings, FanDuel, BetMGM, Caesars, William Hill, Hard Rock, PointsBet, the BetParx group and both prediction markets. Matchday 2 gives you Kalshi and Polymarket, and they are quoting into an empty book.<\/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 API<\/th>\n<th>OddsPapi<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Book coverage<\/td>\n<td>One book per integration, one scraper each<\/td>\n<td>350+ bookmakers, one endpoint<\/td>\n<\/tr>\n<tr>\n<td>Sharp prices<\/td>\n<td>Pinnacle and SBOBet closed to the public<\/td>\n<td>Both on the free tier<\/td>\n<\/tr>\n<tr>\n<td>Asian handicap ladders<\/td>\n<td>Flattened into a single line, if present<\/td>\n<td>Native, 9 rungs with limits<\/td>\n<\/tr>\n<tr>\n<td>Price history<\/td>\n<td>Paid add-on, or you store it yourself<\/td>\n<td>Free <code>\/historical-odds<\/code>, back to June<\/td>\n<\/tr>\n<tr>\n<td>Push updates<\/td>\n<td>Poll and hope<\/td>\n<td>WebSocket feed<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Authenticate and survive the rate limit<\/h2>\n<p>The API key goes in the query string, not a header. The free tier limits per endpoint and returns a structured 429 telling you how long to wait, so build the back-off into the fetcher once.<\/p>\n<pre class=\"wp-block-code\"><code>import requests, time\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE = \"https:\/\/api.oddspapi.io\/v4\"\n\ndef get(endpoint, **params):\n    \"\"\"GET with 429 back-off. Returns None on 404 (an empty window).\"\"\"\n    params[\"apiKey\"] = API_KEY\n    for attempt in range(6):\n        r = requests.get(f\"{BASE}\/{endpoint}\", params=params, timeout=120)\n        if r.status_code == 429:\n            wait = r.json()[\"error\"].get(\"retryMs\", 2000) \/ 1000\n            time.sleep(wait + 0.3)\n            continue\n        if r.status_code == 404:\n            return None\n        r.raise_for_status()\n        return r.json()\n    return None<\/code><\/pre>\n<p>Two things that will bite you. A 429 body is valid JSON, so code that only checks for a <code>bookmakerOdds<\/code> key reads a rate limit as &#8220;no coverage&#8221;. And an empty fixture window returns 404 rather than an empty list, which kills any loop that calls <code>raise_for_status()<\/code> without a guard.<\/p>\n<h2>Step 2: Resolve Serie A, and only Serie A<\/h2>\n<p>The soccer catalogue holds 26 tournaments with &#8220;Serie A&#8221; in the name. Brazil has eight of them. Italy alone lists 48 competitions, including Serie B, Serie C and nine Serie D groups. Filter on the name and the category together.<\/p>\n<pre class=\"wp-block-code\"><code>def find_serie_a():\n    rows = get(\"tournaments\", sportId=10)\n    hits = [t for t in rows\n            if t[\"tournamentName\"] == \"Serie A\" and t.get(\"categoryName\") == \"Italy\"]\n    return hits[0]\n\nt = find_serie_a()\nprint(t[\"tournamentId\"], t[\"tournamentName\"], t[\"futureFixtures\"])\n# 23 Serie A 50<\/code><\/pre>\n<p>Resolve it once, then hardcode 23. The same trap catches the <a href=\"https:\/\/oddspapi.io\/blog\/la-liga-odds-api\/\">La Liga feed<\/a> and the <a href=\"https:\/\/oddspapi.io\/blog\/premier-league-odds-api\/\">Premier League feed<\/a>, where 35 different competitions answer to &#8220;Premier League&#8221;.<\/p>\n<h2>Step 3: Pull the fixture list<\/h2>\n<p>The <code>\/fixtures<\/code> window maxes out at 10 days, and <code>tournamentId<\/code> filters it server-side even though the parameter is undocumented. One more quirk: <code>to<\/code> is a midnight instant, not a whole day, so a query ending on your last match day returns only the games kicking off at exactly 00:00Z. Ask for one extra day.<\/p>\n<pre class=\"wp-block-code\"><code>from datetime import date, timedelta\n\nSERIE_A = 23\n\ndef serie_a_fixtures(start, days=30):\n    \"\"\"Walk 10-day windows. `to` is a midnight instant, so always ask for one extra day.\"\"\"\n    out = {}\n    cur = start\n    while cur &lt; start + timedelta(days=days):\n        end = min(cur + timedelta(days=10), start + timedelta(days=days))\n        rows = get(\"fixtures\", sportId=10, tournamentId=SERIE_A,\n                   **{\"from\": cur.isoformat(), \"to\": (end + timedelta(days=1)).isoformat()})\n        for f in rows or []:\n            out[f[\"fixtureId\"]] = f\n        cur = end\n        time.sleep(1.1)\n    return sorted(out.values(), key=lambda f: f[\"startTime\"])\n\nfixtures = serie_a_fixtures(date(2026, 8, 18), days=30)\nprint(len(fixtures), sum(1 for f in fixtures if f[\"hasOdds\"]))\n# 40 20<\/code><\/pre>\n<p>Fixtures carry <code>participant1Name<\/code> and <code>participant2Name<\/code>, which the <code>\/odds<\/code> response does not. Keep the fixture rows around and join on <code>fixtureId<\/code> when you want team names on your prices.<\/p>\n<h2>Step 4: Parse the board with the guards that matter<\/h2>\n<p>The odds payload nests five levels deep: bookmaker, then market, then outcome, then the <code>players<\/code> dict, then the price object. On game lines the players dict has one key, <code>\"0\"<\/code>. Three separate flags can invalidate a quote and they live at three different levels.<\/p>\n<pre class=\"wp-block-code\"><code>def three_way(fixture_id):\n    \"\"\"Return {slug: (home, draw, away)} for every book with a COMPLETE, live 1X2.\"\"\"\n    data = get(\"odds\", fixtureId=fixture_id)\n    board = {}\n    for slug, book in (data.get(\"bookmakerOdds\") or {}).items():\n        if book.get(\"suspended\") or book.get(\"bookmakerIsActive\") is False:\n            continue\n        market = (book.get(\"markets\") or {}).get(\"101\")\n        if not market:\n            continue\n        prices = []\n        for outcome_id in (\"101\", \"102\", \"103\"):\n            outcome = (market.get(\"outcomes\") or {}).get(outcome_id)\n            price = (outcome or {}).get(\"players\", {}).get(\"0\")\n            if not price or price.get(\"active\") is False or not price.get(\"price\"):\n                prices = []\n                break\n            prices.append(price[\"price\"])\n        if len(prices) == 3:\n            board[slug] = tuple(prices)\n    return board\n\ndef margin(prices):\n    return (sum(1 \/ p for p in prices) - 1) * 100\n\nOPENER = \"id1000002371944898\"       # Inter Milano v AC Monza, Aug 22\nboard = three_way(OPENER)\nprint(len(board))\n# 15<\/code><\/pre>\n<p>Seventeen books ship a payload and 15 survive the guards. BetRivers arrives with <code>suspended: true<\/code> and prices still attached. Bet365 ships a <strong>partial 1X2<\/strong>, a draw and an away price with no home price, on 4 of the 10 opening-round fixtures. Compute a margin off two of three outcomes and you get a number that looks tight and means nothing.<\/p>\n<h3>Then dedupe the shared feeds<\/h3>\n<p>Several slugs quote byte-identical prices while <code>\/bookmakers<\/code> reports <code>cloneOf: null<\/code> for all of them. Averaging across them triple-counts one opinion.<\/p>\n<pre class=\"wp-block-code\"><code>def dedupe(board):\n    groups = {}\n    for slug, prices in board.items():\n        groups.setdefault(prices, []).append(slug)\n    return groups\n\nfor prices, slugs in dedupe(board).items():\n    if len(slugs) &gt; 1:\n        print(slugs)\n# ['betmgm', 'borgata']\n# ['betparx', 'ballybet', 'fourwinds', 'hardrockbet']\n# ['caesars', 'williamhill']<\/code><\/pre>\n<p>Fifteen usable slugs collapse to <strong>10 independent quotes<\/strong>. BetMGM\/Borgata and Caesars\/William Hill are the known shared feeds. Hard Rock joining the BetParx group is a coincidence at round numbers on this fixture, not a shared feed, which is why you dedupe per fixture rather than keeping a static clone list.<\/p>\n<h2>Who is tight and who is wide<\/h2>\n<p>Median three-way margin across all 10 opening-round fixtures:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Book<\/th>\n<th>Median 1X2 margin<\/th>\n<th>Note<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>kalshi<\/td>\n<td>1.99%<\/td>\n<td>prediction market<\/td>\n<\/tr>\n<tr>\n<td>polymarket<\/td>\n<td>1.99%<\/td>\n<td>prediction market<\/td>\n<\/tr>\n<tr>\n<td><strong>pinnacle<\/strong><\/td>\n<td><strong>3.73%<\/strong><\/td>\n<td>tightest sportsbook<\/td>\n<\/tr>\n<tr>\n<td>betmgm = borgata<\/td>\n<td>5.77%<\/td>\n<td>shared feed<\/td>\n<\/tr>\n<tr>\n<td>pointsbet.com.au<\/td>\n<td>5.90%<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>caesars = williamhill<\/td>\n<td>5.91%<\/td>\n<td>shared feed<\/td>\n<\/tr>\n<tr>\n<td>hardrockbet<\/td>\n<td>7.20%<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>betparx = ballybet = betrivers = fourwinds<\/td>\n<td>7.23%<\/td>\n<td>shared feed<\/td>\n<\/tr>\n<tr>\n<td>fanduel<\/td>\n<td>7.26%<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>bet365<\/td>\n<td>9.05%<\/td>\n<td>complete on 6 of 10 only<\/td>\n<\/tr>\n<tr>\n<td>draftkings<\/td>\n<td>9.20%<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>sbobet<\/td>\n<td>10.24%<\/td>\n<td>see the handicap section<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>On the opener, Inter Milano v AC Monza, Kalshi prices 1.25 \/ 7.692 \/ 14.286. That sums to exactly 1.0000, a 0.00% margin. Before you treat that as free money, read the ladder: Kalshi holds $305.60 on the home rung and $90.64 on the away rung. A zero margin on a thin ladder is arithmetic, not an edge, and we <a href=\"https:\/\/oddspapi.io\/blog\/kalshi-vs-polymarket-api\/\">worked through why on the exchange side<\/a>.<\/p>\n<h2>The finding: the horizon sets the price quality, not the venue<\/h2>\n<p>Matchday 2 is 10 days out and Kalshi and Polymarket are the only books on it. The obvious reading is that prediction markets open first and sportsbooks follow. On Serie A that reading is backwards.<\/p>\n<p>Pull the free price history for the opener and you get every book&#8217;s first recorded quote:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>First quote<\/th>\n<th>Days before kick-off<\/th>\n<th>Book<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Jun 18<\/td>\n<td>65<\/td>\n<td>bet365<\/td>\n<\/tr>\n<tr>\n<td>Jun 20<\/td>\n<td>63<\/td>\n<td>hardrockbet, betrivers, ballybet, betparx<\/td>\n<\/tr>\n<tr>\n<td>Jun 21<\/td>\n<td>62<\/td>\n<td>fourwinds<\/td>\n<\/tr>\n<tr>\n<td><strong>Jun 24<\/strong><\/td>\n<td><strong>59<\/strong><\/td>\n<td><strong>pinnacle<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Jun 25<\/td>\n<td>58<\/td>\n<td>sbobet, fanduel<\/td>\n<\/tr>\n<tr>\n<td>Jul 5<\/td>\n<td>48<\/td>\n<td>betmgm, borgata<\/td>\n<\/tr>\n<tr>\n<td>Jul 23<\/td>\n<td>30<\/td>\n<td>caesars, williamhill<\/td>\n<\/tr>\n<tr>\n<td>Jul 30 \/ Jul 31<\/td>\n<td>23 \/ 22<\/td>\n<td>pointsbet.com.au, draftkings<\/td>\n<\/tr>\n<tr>\n<td><strong>Aug 8<\/strong><\/td>\n<td><strong>14<\/strong><\/td>\n<td><strong>kalshi, polymarket<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Pinnacle opened the Serie A season 59 days out. The prediction markets turned up 45 days later and arrived last. Check a matchday 2 fixture and the pattern repeats: Kalshi&#8217;s first recorded quote on Juventus v Parma is 15 August, exactly 14 days out, and a history call for <code>pinnacle,bet365,draftkings<\/code> on that fixture returns nothing at all. No sportsbook has ever quoted it.<\/p>\n<p>So the exchanges list every fixture that falls inside a fixed 14-day rolling window, whether anyone is trading it or not. The sportsbooks open the season opener two months early because the date is fixed and the fixture is marquee, then revert to pricing one round at a time. Matchday 2 shows up on the exchanges because the window reached it on schedule, and it shows up empty because no money has arrived yet.<\/p>\n<h3>What those 14-day-out prices were worth<\/h3>\n<p>Both venues have price history on the opener going back to 8 August, which is the same 14-day horizon matchday 2 sits at today. Compare their opening quote to their current one:<\/p>\n<pre class=\"wp-block-code\"><code>def history_1x2(fixture_id, slug):\n    data = get(\"historical-odds\", fixtureId=fixture_id, bookmakers=slug)\n    market = data[\"bookmakers\"][slug][\"markets\"][\"101\"]\n    return {o: market[\"outcomes\"][o][\"players\"][\"0\"] for o in (\"101\", \"102\", \"103\")}\n\nfor slug in (\"kalshi\", \"polymarket\"):\n    s = history_1x2(OPENER, slug)\n    first = [s[o][0][\"price\"] for o in (\"101\", \"102\", \"103\")]\n    last = [s[o][-1][\"price\"] for o in (\"101\", \"102\", \"103\")]\n    print(slug, first, round(margin(first), 2), \"->\", last, round(margin(last), 2))\n    time.sleep(4.6)\n\n# kalshi     [1.235, 1.515, 6.25] 62.98  -> [1.25, 7.692, 14.286]  0.00\n# polymarket [1.205, 1, 1]       182.99  -> [1.235, 7.143, 14.286] 1.97<\/code><\/pre>\n<p>Polymarket&#8217;s opening quote on Inter Milano v AC Monza was 1.205 on the home side and <strong>1.000 on both the draw and the away win<\/strong>. Kalshi opened with a draw at 1.515, shorter than most draws ever get, for a 62.98% margin. Ten days later the same two venues are the tightest prices on a 17-book board.<\/p>\n<p>Read that as a warning about the matchday 2 board rather than a compliment to the exchanges. The same two venues will be tight on those fixtures in a week. Today they are quoting a number nobody has tested.<\/p>\n<h3>The same measurement across the whole slate<\/h3>\n<p>Both rounds have 10 fixtures. Polymarket lists the identical 55 two-sided markets on each one, so the menus are directly comparable:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Slate<\/th>\n<th>Venue<\/th>\n<th>Two-sided markets<\/th>\n<th>Median margin<\/th>\n<th>Over 50%<\/th>\n<th>Pass &lt;5% and $500 depth<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Matchday 1 (4&ndash;6 days out)<\/td>\n<td>kalshi<\/td>\n<td>110<\/td>\n<td>2.01%<\/td>\n<td>1<\/td>\n<td>43 (39.1%)<\/td>\n<\/tr>\n<tr>\n<td>Matchday 1 (4&ndash;6 days out)<\/td>\n<td>polymarket<\/td>\n<td>550<\/td>\n<td>7.97%<\/td>\n<td>188<\/td>\n<td>39 (7.1%)<\/td>\n<\/tr>\n<tr>\n<td>Matchday 2 (10&ndash;13 days out)<\/td>\n<td>polymarket<\/td>\n<td>550<\/td>\n<td><strong>98.02%<\/strong><\/td>\n<td><strong>550<\/strong><\/td>\n<td><strong>0<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Every one of the 550 two-sided markets on the matchday 2 slate is wider than a 50% margin. Not one clears a basic quality screen. The market count is the same, the flags are the same, and the numbers are noise.<\/p>\n<h2>The screen: score the board before you trade it<\/h2>\n<p>Market count tells you nothing here. Ladder depth on its own tells you nothing either, because the matchday 2 quotes carry $122 of real median depth behind a 98% margin. Score margin and depth together.<\/p>\n<pre class=\"wp-block-code\"><code>def two_sided_quality(fixture_id, slug):\n    \"\"\"Median margin and usable-market count for one exchange on one fixture.\"\"\"\n    data = get(\"odds\", fixtureId=fixture_id)\n    book = (data.get(\"bookmakerOdds\") or {}).get(slug)\n    if not book:\n        return None\n    margins, usable = [], 0\n    for market in (book.get(\"markets\") or {}).values():\n        outcomes = list((market.get(\"outcomes\") or {}).values())\n        if len(outcomes) != 2:\n            continue\n        prices = [o.get(\"players\", {}).get(\"0\") for o in outcomes]\n        if not all(p and p.get(\"price\") for p in prices):\n            continue\n        m = margin([p[\"price\"] for p in prices])\n        depth = min(sum(rung[\"limit\"] for rung in\n                        (p.get(\"exchangeMeta\") or {}).get(\"back\", []))\n                    for p in prices)\n        margins.append(m)\n        if m &lt; 5 and depth &gt;= 500:\n            usable += 1\n    margins.sort()\n    return {\"markets\": len(margins),\n            \"median_margin\": round(margins[len(margins) \/\/ 2], 2),\n            \"usable\": usable}\n\nprint(two_sided_quality(OPENER, \"polymarket\"))\n# {'markets': 55, 'median_margin': 5.99, 'usable': 5}\nprint(two_sided_quality(\"id1000002371945218\", \"polymarket\"))   # AC Milan v Venezia, Aug 28\n# {'markets': 55, 'median_margin': 98.02, 'usable': 0}<\/code><\/pre>\n<p>Same code, same menu size, two fixtures six days apart on the calendar. One returns five tradeable markets and the other returns zero. Run this before you let a fixture into a scanner and the placeholder round drops out on its own. The <code>exchangeMeta<\/code> ladder shape varies between exchange slugs, so parse it defensively and read the <a href=\"https:\/\/oddspapi.io\/blog\/betting-limits-api-stake-sizing\/\">limits and depth guide<\/a> before you size anything off it.<\/p>\n<h2>Asian handicaps: where SBOBet stops being the worst book<\/h2>\n<p>SBOBet posts the widest three-way on the Serie A board at 10.24%. On the handicap it posts one of the tightest lines available. Sharps price the market they want action on and park the one they do not.<\/p>\n<p>Handicap lines each get their own market ID, so resolve by name and handicap value rather than hardcoding an integer.<\/p>\n<pre class=\"wp-block-code\"><code>NAMES = {m[\"marketId\"]: (m[\"marketName\"], m.get(\"handicap\"), m.get(\"period\"))\n         for m in get(\"markets\", sportId=10)}\n\ndef handicap_ladder(fixture_id, slug):\n    data = get(\"odds\", fixtureId=fixture_id)\n    book = (data.get(\"bookmakerOdds\") or {}).get(slug, {})\n    rungs = []\n    for market_id, market in (book.get(\"markets\") or {}).items():\n        name, line, period = NAMES.get(int(market_id), (\"\", None, None))\n        if name != \"Asian Handicap\" or period != \"fulltime\":\n            continue\n        outcomes = sorted((market.get(\"outcomes\") or {}).keys())\n        if len(outcomes) != 2:\n            continue\n        prices = [market[\"outcomes\"][o][\"players\"][\"0\"] for o in outcomes]\n        if not all(p.get(\"price\") and p.get(\"active\") is not False for p in prices):\n            continue\n        limit = prices[0].get(\"limit\")          # SBOBet and US retail ship None here\n        base = None\n        if limit:\n            base = round(limit if prices[0][\"price\"] &gt;= 2\n                         else limit * (prices[0][\"price\"] - 1))\n        rungs.append((line, margin([p[\"price\"] for p in prices]),\n                      prices[0][\"price\"], prices[1][\"price\"], base))\n    return sorted(rungs)\n\nfor line, m, home, away, base in handicap_ladder(OPENER, \"pinnacle\"):\n    print(f\"{line:>6}  {home:>6} \/ {away:<6}  {m:5.2f}%  base ${base}\")<\/code><\/pre>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Line<\/th>\n<th>Pinnacle price<\/th>\n<th>Margin<\/th>\n<th>Implied base<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>-2.75<\/td>\n<td>3.40 \/ 1.340<\/td>\n<td>4.04%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-2.5<\/td>\n<td>2.80 \/ 1.462<\/td>\n<td>4.11%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-2.25<\/td>\n<td>2.49 \/ 1.574<\/td>\n<td>3.69%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-2<\/td>\n<td>2.18 \/ 1.735<\/td>\n<td>3.51%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td><strong>-1.75<\/strong><\/td>\n<td><strong>1.884 \/ 2.000<\/strong><\/td>\n<td><strong>3.08%<\/strong><\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-1.5<\/td>\n<td>1.689 \/ 2.250<\/td>\n<td>3.65%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-1.25<\/td>\n<td>1.495 \/ 2.720<\/td>\n<td>3.65%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-1<\/td>\n<td>1.297 \/ 3.710<\/td>\n<td>4.06%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<tr>\n<td>-0.75<\/td>\n<td>1.243 \/ 4.240<\/td>\n<td>4.04%<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Nine rungs, and the margin traces a U with its floor at -1.75, the rung nearest the true number. The raw <code>limit<\/code> field swings from $1,000 to $4,115 across the ladder, but back out the base with <code>limit &times; (price - 1)<\/code> and it returns $1,000 on every rung. One appetite, priced nine ways.<\/p>\n<p>SBOBet walks three rungs and charges <strong>2.85% at -1.75<\/strong>, beating Pinnacle on the same line while quoting 10.50% on the three-way beside it. That split now holds on Serie A, the Premier League, La Liga and three other competitions, and there is <a href=\"https:\/\/oddspapi.io\/blog\/why-sharps-bet-asian-handicap\/\">a nine-fixture margin study on why<\/a>. If you need to settle quarter lines, the <a href=\"https:\/\/oddspapi.io\/blog\/asian-handicap-calculator-python\/\">Asian handicap calculator<\/a> covers the arithmetic.<\/p>\n<h2>Fair prices and the best number on the board<\/h2>\n<p>Strip Pinnacle's margin with the power method and you get a reference the rest of the board can be measured against.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Inter Milano v AC Monza<\/th>\n<th>Home<\/th>\n<th>Draw<\/th>\n<th>Away<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Pinnacle raw (4.36%)<\/td>\n<td>1.211<\/td>\n<td>7.05<\/td>\n<td>13.16<\/td>\n<\/tr>\n<tr>\n<td>Fair probability<\/td>\n<td>81.4%<\/td>\n<td>12.3%<\/td>\n<td>6.3%<\/td>\n<\/tr>\n<tr>\n<td>Fair odds<\/td>\n<td>1.228<\/td>\n<td>8.138<\/td>\n<td>15.903<\/td>\n<\/tr>\n<tr>\n<td>Best available price<\/td>\n<td>1.25 (kalshi)<\/td>\n<td>7.692 (kalshi)<\/td>\n<td>15.00 (pointsbet.com.au)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Best available price means the top number on this board at this moment. It is not a value call, and the Kalshi rungs behind those two prices hold $305 and $147. The <a href=\"https:\/\/oddspapi.io\/blog\/no-vig-odds-api\/\">no-vig methods<\/a> and the <a href=\"https:\/\/oddspapi.io\/blog\/consensus-odds-fair-odds-calculator-python\/\">consensus calculator<\/a> go deeper on the de-vig itself, and <a href=\"https:\/\/oddspapi.io\/blog\/line-shopping-python-best-odds\/\">line shopping in Python<\/a> generalises the best-price loop across the slate.<\/p>\n<h2>Player props are live on Serie A<\/h2>\n<p>The opening round carries 11 prop families and 715 player-level prices. Anytime Goal Scorer is the widest, on 8 books.<\/p>\n<pre class=\"wp-block-code\"><code>def goalscorer_prices(fixture_id):\n    data = get(\"odds\", fixtureId=fixture_id)\n    rows = []\n    for slug, book in (data.get(\"bookmakerOdds\") or {}).items():\n        for market_id, market in (book.get(\"markets\") or {}).items():\n            if NAMES.get(int(market_id), (\"\",))[0] != \"Anytime Goal Scorer\":\n                continue\n            for outcome in (market.get(\"outcomes\") or {}).values():\n                for player_id, price in (outcome.get(\"players\") or {}).items():\n                    if player_id == \"0\":          # \"0\" is the game line, not a player\n                        continue\n                    rows.append((price.get(\"playerName\"), slug, price.get(\"price\")))\n    return rows\n\nprops = goalscorer_prices(OPENER)\nprint(len(props), sorted({slug for _, slug, _ in props}))\n# 192 ['ballybet', 'betparx', 'betrivers', 'draftkings', 'fanduel',\n#      'fourwinds', 'hardrockbet', 'polymarket']<\/code><\/pre>\n<p>The <code>players<\/code> dict is keyed by player ID on prop markets, not by <code>\"0\"<\/code>. Hardcode <code>players[\"0\"]<\/code> the way the game-line examples do and every prop market reads as empty. Player names arrive as <code>\"Last, First\"<\/code>.<\/p>\n<p>DraftKings runs the deepest prop menu on the league and is the only book quoting tackles, fouls committed, offsides and last goalscorer. The <a href=\"https:\/\/oddspapi.io\/blog\/player-props-api-nfl-nba-mlb-odds-python\/\">player props guide<\/a> covers the same parsing pattern across the US sports.<\/p>\n<h2>Free price history, and the limit ramp<\/h2>\n<p><code>\/historical-odds<\/code> is on the free tier. It takes three bookmakers per call and runs slower than <code>\/odds<\/code>, so sleep about 4.5 seconds between calls. Snapshots keep recording past kick-off, so filter on <code>createdAt &lt; startTime<\/code> if you want a true closing line.<\/p>\n<p>Pinnacle's limit base is the most honest confidence signal in the feed. On the opener it went:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Date<\/th>\n<th>Days out<\/th>\n<th>Price<\/th>\n<th>Implied base<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Jun 24<\/td>\n<td>59<\/td>\n<td>1.201<\/td>\n<td>$250<\/td>\n<\/tr>\n<tr>\n<td>Jul 30<\/td>\n<td>23<\/td>\n<td>1.211<\/td>\n<td>$500<\/td>\n<\/tr>\n<tr>\n<td>Aug 16<\/td>\n<td>6<\/td>\n<td>1.221<\/td>\n<td>$1,000<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Forty snapshots, 23 price changes, and the price finished within a tick of where it opened. The number was right in June. Pinnacle just was not willing to take much on it. A $250 base is a placeholder, and the same figure shows up on the opening rounds of every European league before it ramps.<\/p>\n<h2>Honest coverage notes<\/h2>\n<ul>\n<li><strong>The Italian books quote every fixture, but none of them wins the price.<\/strong> Re-measured on 24 August 2026 across four fixtures, nine Italian-licensed slugs priced all four: <code>888sport.it<\/code>, <code>admiralbet.it<\/code>, <code>bet365.it<\/code>, <code>eurobet.it<\/code>, <code>goldbet.it<\/code>, <code>leovegas.it<\/code>, <code>netbet.it<\/code>, <code>quigioco.it<\/code> and <code>sisal.it<\/code>. They land mid-table in a 160-book field on median 1X2 margin: <code>netbet.it<\/code> 5.02% (rank 42), <code>888sport.it<\/code> 5.18% (46), <code>admiralbet.it<\/code> 5.37% (53), <code>sisal.it<\/code> 5.54% (56), <code>eurobet.it<\/code> 5.76% (70), <code>goldbet.it<\/code> 5.84% (71), <code>bet365.it<\/code> 6.65% (99), <code>quigioco.it<\/code> 6.88% (106), <code>leovegas.it<\/code> 10.43% (153). <code>pinnacle<\/code> sits at 3.40%, rank 16, so the best Italian book still charges you 1.6 points more than the sharp. The top of the board is <code>betfair-ex<\/code> 0.87%, <code>polymarket<\/code> 1.01%, <code>polymarket.us<\/code> 1.01%, <code>kalshi<\/code> 1.50% and <code>1xbet<\/code> 1.55%. Caveat: <code>snai.it<\/code>, <code>lottomatica.it<\/code> and <code>planetwin365.it<\/code> appeared on none of the four fixtures, so treat those three as unavailable until you probe them yourself.<\/li>\n<li><strong>Corners exist, sharps do not price them.<\/strong> Sixty corner markets on the opener across 8 slugs, which dedupe to three independent feeds. Pinnacle prices La Liga corners and skips Serie A, so probe the competition rather than assuming.<\/li>\n<li><strong>Zero card or booking markets<\/strong> anywhere on the league.<\/li>\n<li><strong>Bet365 partial three-ways.<\/strong> Four of ten fixtures. Require all three outcomes before you compute anything.<\/li>\n<\/ul>\n<h2>Where to go next<\/h2>\n<p>The parsing here is soccer-wide, so it drops straight onto the <a href=\"https:\/\/oddspapi.io\/blog\/football-odds-api-soccer-data\/\">general football odds API<\/a>, the <a href=\"https:\/\/oddspapi.io\/blog\/premier-league-odds-api\/\">Premier League<\/a> and <a href=\"https:\/\/oddspapi.io\/blog\/la-liga-odds-api\/\">La Liga<\/a>. If you are polling every 60 seconds to catch line moves, stop, and take the <a href=\"https:\/\/oddspapi.io\/blog\/websocket-odds-api-real-time-betting-data\/\">WebSocket feed<\/a> instead.<\/p>\n<p>Every number in this post came off the free tier: 350+ bookmakers, sharp books included, native Asian handicap ladders, and price history back to June at no cost. <a href=\"https:\/\/oddspapi.io\/\">Get your free API key<\/a> and pull the Serie A board yourself before Saturday.<\/p>\n<h2>FAQ<\/h2>\n<h3>Is there a free Serie A odds API?<\/h3>\n<p>Yes. OddsPapi's free tier covers Serie A across 350+ bookmakers, including Pinnacle and SBOBet, with live odds, Asian handicap ladders, player props and free historical price data. Authentication is an <code>apiKey<\/code> query parameter.<\/p>\n<h3>What is the Serie A tournament ID?<\/h3>\n<p>Serie A is <code>tournamentId<\/code> 23 with <code>categoryName<\/code> \"Italy\". The soccer catalogue holds 26 tournaments named \"Serie A\", eight of them Brazilian, so match on the name and the category together before you hardcode the ID.<\/p>\n<h3>How many bookmakers price a Serie A match?<\/h3>\n<p>Seventeen books quote the opening round four days out. Fifteen of them ship a complete, unsuspended three-way, and those 15 dedupe to 10 independent quotes once you group the shared feeds. Rounds further out carry far fewer.<\/p>\n<h3>Why does a fixture show hasOdds true with no real prices?<\/h3>\n<p><code>hasOdds<\/code> tells you a payload exists, not that anyone is trading. On the Serie A round 10 days out, the only two venues quoting publish three-way margins of 139&ndash;152%, with 15 of 20 three-ways carrying the same price on all three outcomes. Screen on margin and ladder depth, never on the flag.<\/p>\n<h3>Does the Serie A feed include player props?<\/h3>\n<p>Yes. The opening round carries 11 prop families and 715 player-level prices, with Anytime Goal Scorer on 8 books. On prop markets the <code>players<\/code> dict is keyed by player ID rather than <code>\"0\"<\/code>, so iterate it instead of indexing.<\/p>\n<h3>Can I get historical Serie A odds for free?<\/h3>\n<p>Yes. <code>\/historical-odds<\/code> is on the free tier and returns the full snapshot history per outcome. The season opener carries Pinnacle snapshots back to 24 June. Pass at most three bookmakers per call and sleep about 4.5 seconds between calls.<\/p>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\"@type\":\"Question\",\"name\":\"Is there a free Serie A odds API?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. OddsPapi's free tier covers Serie A across 350+ bookmakers, including Pinnacle and SBOBet, with live odds, Asian handicap ladders, player props and free historical price data. Authentication is an apiKey query parameter.\"}},\n    {\"@type\":\"Question\",\"name\":\"What is the Serie A tournament ID?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Serie A is tournamentId 23 with categoryName Italy. The soccer catalogue holds 26 tournaments named Serie A, eight of them Brazilian, so match on the name and the category together before you hardcode the ID.\"}},\n    {\"@type\":\"Question\",\"name\":\"How many bookmakers price a Serie A match?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Seventeen books quote the opening round four days out. Fifteen ship a complete, unsuspended three-way, and those 15 dedupe to 10 independent quotes once you group the shared feeds. Rounds further out carry far fewer.\"}},\n    {\"@type\":\"Question\",\"name\":\"Why does a fixture show hasOdds true with no real prices?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"hasOdds tells you a payload exists, not that anyone is trading. On the Serie A round 10 days out, the only two venues quoting publish three-way margins of 139 to 152 percent, with 15 of 20 three-ways carrying the same price on all three outcomes. Screen on margin and ladder depth, never on the flag.\"}},\n    {\"@type\":\"Question\",\"name\":\"Does the Serie A feed include player props?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. The opening round carries 11 prop families and 715 player-level prices, with Anytime Goal Scorer on 8 books. On prop markets the players dict is keyed by player ID rather than 0, so iterate it instead of indexing.\"}},\n    {\"@type\":\"Question\",\"name\":\"Can I get historical Serie A odds for free?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. The historical-odds endpoint is on the free tier and returns the full snapshot history per outcome. The season opener carries Pinnacle snapshots back to 24 June. Pass at most three bookmakers per call and sleep about 4.5 seconds between calls.\"}}\n  ]\n}\n<\/script><\/p>\n<p><!--\nFocus Keyphrase: serie a odds api\nSEO Title: Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14\nMeta Description: Pull live Serie A odds from 190+ bookmakers in Python. Pinnacle and nine Italian books, plus the date each book first priced the opening round.\nSlug: serie-a-odds-api\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pull live Serie A odds from 190+ bookmakers in Python. Pinnacle and nine Italian books, plus the date each book first priced the opening round.<\/p>\n","protected":false},"author":2,"featured_media":3670,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[8,9,11,90,15],"class_list":["post-3669","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-free-api","tag-odds-api","tag-python","tag-serie-a","tag-soccer"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | 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\/serie-a-odds-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"Pull live Serie A odds from 190+ bookmakers in Python. Pinnacle and nine Italian books, plus the date each book first priced the opening round.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-22T10:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-07T14:01:48+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-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=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14\",\"datePublished\":\"2026-08-22T10:00:00+00:00\",\"dateModified\":\"2026-09-07T14:01:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\"},\"wordCount\":2399,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp\",\"keywords\":[\"Free API\",\"Odds API\",\"Python\",\"Serie A\",\"Soccer\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\",\"name\":\"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp\",\"datePublished\":\"2026-08-22T10:00:00+00:00\",\"dateModified\":\"2026-09-07T14:01:48+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp\",\"width\":2560,\"height\":1429,\"caption\":\"Serie A Odds API - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14\"}]},{\"@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":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | 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\/serie-a-odds-api\/","og_locale":"en_US","og_type":"article","og_title":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | OddsPapi Blog","og_description":"Pull live Serie A odds from 190+ bookmakers in Python. Pinnacle and nine Italian books, plus the date each book first priced the opening round.","og_url":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-08-22T10:00:00+00:00","article_modified_time":"2026-09-07T14:01:48+00:00","og_image":[{"width":2560,"height":1429,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-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":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14","datePublished":"2026-08-22T10:00:00+00:00","dateModified":"2026-09-07T14:01:48+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/"},"wordCount":2399,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp","keywords":["Free API","Odds API","Python","Serie A","Soccer"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/","url":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/","name":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14 | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp","datePublished":"2026-08-22T10:00:00+00:00","dateModified":"2026-09-07T14:01:48+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/08\/serie-a-odds-api-scaled.webp","width":2560,"height":1429,"caption":"Serie A Odds API - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/serie-a-odds-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"Serie A Odds API: bet365 Opens 65 Days Out, Kalshi at 14"}]},{"@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\/3669","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=3669"}],"version-history":[{"count":4,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3669\/revisions"}],"predecessor-version":[{"id":3893,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3669\/revisions\/3893"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/3670"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=3669"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=3669"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=3669"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}