{"id":3171,"date":"2026-08-07T10:00:00","date_gmt":"2026-08-07T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=3171"},"modified":"2026-08-29T15:03:04","modified_gmt":"2026-08-29T15:03:04","slug":"champions-league-odds-api","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/","title":{"rendered":"Champions League Odds API: 17 Books on Every UEFA Tie"},"content":{"rendered":"<p>UEFA does not sell a public Champions League odds API. There is no developer portal, no self-serve key, and no endpoint that returns what 17 bookmakers think about tonight&#8217;s qualifier. What exists instead is a scattering of scraped feeds, enterprise contracts that start with a sales call, and generic football APIs that refresh their odds once a day.<\/p>\n<p>You can get the data anyway. This guide pulls live Champions League, Europa League and Conference League prices from 383 bookmakers through one endpoint, in Python, on a free key. Every number below came off the live API on 29 July 2026 while the qualifying round was being priced.<\/p>\n<h2>What the European club stack actually looks like in the feed<\/h2>\n<p>UEFA runs three club competitions, and OddsPapi carries all three as separate tournaments under soccer (<code>sportId=10<\/code>). Here is what the API returned for the next nine days:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Competition<\/th>\n<th>tournamentId<\/th>\n<th>Fixtures<\/th>\n<th>With odds<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>UEFA Champions League<\/td>\n<td>7<\/td>\n<td>18<\/td>\n<td>12<\/td>\n<\/tr>\n<tr>\n<td>UEFA Europa League<\/td>\n<td>679<\/td>\n<td>24<\/td>\n<td>10<\/td>\n<\/tr>\n<tr>\n<td>UEFA Conference League<\/td>\n<td>34480<\/td>\n<td>77<\/td>\n<td>45<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Two details matter before you write a single parser.<\/p>\n<p>First, the qualifying bracket is published before anyone knows who is in it. Of the 119 European fixtures in that window, 33 carried participant names like <code>Winner Match 11<\/code> against <code>Winner Match 6<\/code>. They are real fixture objects with real IDs and real kick-off times, and they all return <code>hasOdds: false<\/code>. Filter them out or your fixture list fills with ties that have no teams.<\/p>\n<p>Second, book depth is a function of how close kick-off is. The eight ties kicking off within hours carried 15 to 17 bookmakers each. The ties five days out carried one. Books post European qualifiers roughly two days ahead, so a census run on a quiet Tuesday will understate coverage badly.<\/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 generic football API<\/th>\n<th>OddsPapi<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Books per tie<\/td>\n<td>One at a time, or a handful<\/td>\n<td>15 to 17 in one call<\/td>\n<\/tr>\n<tr>\n<td>Sharp pricing<\/td>\n<td>Rarely included<\/td>\n<td>Pinnacle and SBOBet on every priced tie<\/td>\n<\/tr>\n<tr>\n<td>Refresh<\/td>\n<td>Often once a day<\/td>\n<td>Live, with WebSocket push<\/td>\n<\/tr>\n<tr>\n<td>Corners, correct score<\/td>\n<td>Usually dropped<\/td>\n<td>64 corner markets, correct score to 57 scorelines<\/td>\n<\/tr>\n<tr>\n<td>Price history<\/td>\n<td>Paid add-on<\/td>\n<td>Free tier<\/td>\n<\/tr>\n<tr>\n<td>Access<\/td>\n<td>Sales call<\/td>\n<td>Self-serve key<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Authenticate and find the competitions<\/h2>\n<p>The key rides as a query parameter on every call. It is never a header.<\/p>\n<pre class=\"wp-block-code\"><code>import time\nimport requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\n\ndef get(path, **params):\n    params[\"apiKey\"] = API_KEY\n    r = requests.get(f\"{BASE_URL}{path}\", params=params)\n    r.raise_for_status()\n    time.sleep(1.0)          # the free tier rate-limits per endpoint\n    return r.json()\n\n\ndef european_tournaments():\n    wanted = {\"UEFA Champions League\", \"UEFA Europa League\", \"UEFA Conference League\"}\n    return {\n        t[\"tournamentName\"]: t[\"tournamentId\"]\n        for t in get(\"\/tournaments\", sportId=10)\n        if t[\"tournamentName\"] in wanted\n    }\n\n\nfor name, tid in european_tournaments().items():\n    print(f\"{tid:>6}  {name}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>     7  UEFA Champions League\n   679  UEFA Europa League\n34480  UEFA Conference League<\/code><\/pre>\n<p>Resolve the IDs at runtime rather than pasting them into a config file. Tournament IDs are stable across a season, but new competitions and women&#8217;s variants share the same name prefixes, and hardcoding is how you end up parsing the Women&#8217;s Champions League by accident.<\/p>\n<h2>Step 2: Pull fixtures and drop the placeholders<\/h2>\n<p><code>\/fixtures<\/code> takes a date range of up to ten days. Filter on <code>hasOdds<\/code> and on the bracket placeholder pattern in the same pass.<\/p>\n<pre class=\"wp-block-code\"><code>from datetime import datetime, timedelta, timezone\n\n\ndef priced_fixtures(tournament_name, days=9):\n    today = datetime.now(timezone.utc)\n    fixtures = get(\"\/fixtures\", sportId=10, **{\n        \"from\": today.strftime(\"%Y-%m-%d\"),\n        \"to\": (today + timedelta(days=days)).strftime(\"%Y-%m-%d\"),\n    })\n    return [\n        f for f in fixtures\n        if f[\"tournamentName\"] == tournament_name\n        and f[\"hasOdds\"]\n        and \"Winner Match\" not in f[\"participant1Name\"]\n    ]\n\n\nfor f in priced_fixtures(\"UEFA Champions League\"):\n    print(f[\"fixtureId\"], f[\"participant1Name\"], \"v\", f[\"participant2Name\"])<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>id1000000772176812 FC Kairat Almaty v AC Omonia Nicosia\nid1000000772176794 FK Kauno Zalgiris v KI Klaksvik\nid1000000772176798 KKS Lech Poznan v AGF Aarhus\nid1000000772176808 CS Universitatea Craiova v PFC Levski Sofia\nid1000000772176838 Hapoel Be`er Sheva FC v Vikingur Reykjavik\nid1000000772176722 FK Crvena Zvezda Belgrade v Larne FC\nid1000000772177116 Gornik Zabrze v Fenerbahce Istanbul\nid1000000772176844 SK Slovan Bratislava v FC Iberia 1999<\/code><\/pre>\n<p>Team names live on <code>participant1Name<\/code> and <code>participant2Name<\/code>. The nested <code>participants<\/code> list on the fixture object comes back empty, so do not reach for it.<\/p>\n<h2>Step 3: Build the market lookup<\/h2>\n<p>Soccer carries 32,815 market IDs once you count every handicap and total line as its own market. Nobody should hardcode that. Pull the catalog once and resolve markets by name, handicap and period.<\/p>\n<pre class=\"wp-block-code\"><code>CATALOG = get(\"\/markets\", sportId=10)\n\nOUTCOME_NAME = {\n    (m[\"marketId\"], o[\"outcomeId\"]): o[\"outcomeName\"]\n    for m in CATALOG for o in m.get(\"outcomes\", [])\n}\n\n\ndef find_market(name, handicap=0, period=\"fulltime\"):\n    for m in CATALOG:\n        if (m[\"marketName\"] == name\n                and m.get(\"handicap\") == handicap\n                and m.get(\"period\") == period):\n            return m[\"marketId\"]\n    return None\n\n\nFT_RESULT = find_market(\"Full Time Result\")\nprint(FT_RESULT)      # 101<\/code><\/pre>\n<p>The 1X2 outcomes come back labelled <code>1<\/code>, <code>X<\/code> and <code>2<\/code>, matching the European convention rather than home\/draw\/away.<\/p>\n<h2>Step 4: Read every book on one tie<\/h2>\n<p>The worked example is Gornik Zabrze against Fenerbahce Istanbul (<code>id1000000772177116<\/code>), a second qualifying round tie that drew 15 bookmakers.<\/p>\n<pre class=\"wp-block-code\"><code>def read_market(fixture_id, market_id):\n    payload = get(\"\/odds\", fixtureId=fixture_id)\n    table = {}\n    for slug, data in payload.get(\"bookmakerOdds\", {}).items():\n        market = data[\"markets\"].get(str(market_id))\n        if not market:\n            continue\n        prices = {}\n        for outcome_id, outcome in market[\"outcomes\"].items():\n            quote = outcome[\"players\"].get(\"0\")\n            if not quote or quote.get(\"active\") is False:\n                continue\n            label = OUTCOME_NAME.get((market_id, int(outcome_id)), outcome_id)\n            prices[label] = quote[\"price\"]\n        if prices:\n            table[slug] = prices\n    return table\n\n\nboard = read_market(\"id1000000772177116\", FT_RESULT)\nfor slug, prices in sorted(board.items()):\n    print(f\"{slug:20s} {prices}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>bet365               {'1': 5.75, 'X': 4.33, '2': 1.48}\nbetmgm               {'1': 6.25, 'X': 4.5, '2': 1.49}\nbetparx              {'1': 6.5, 'X': 4.3, '2': 1.47}\nborgata              {'1': 6.25, 'X': 4.5, '2': 1.49}\ndraftkings           {'1': 6.0, 'X': 4.3, '2': 1.51}\nfanduel              {'1': 6.0, 'X': 4.2, '2': 1.48}\nfourwinds            {'1': 6.5, 'X': 4.3, '2': 1.47}\nhardrockbet          {'1': 6.5, 'X': 4.25, '2': 1.476}\nkalshi               {'1': 7.143, 'X': 4.545, '2': 1.515}\npinnacle             {'1': 6.02, 'X': 4.24, '2': 1.51}\npointsbet.com.au     {'1': 6.0, 'X': 4.5, '2': 1.48}\npolymarket           {'1': 6.667, 'X': 4.545, '2': 1.562}\nsbobet               {'1': 5.5, 'X': 3.93, '2': 1.45}<\/code><\/pre>\n<p>Filter on <code>active is False<\/code> rather than on a truthy <code>active<\/code>. The live feed ships <code>active: null<\/code> next to perfectly valid prices on pre-match fixtures, and a truthy test silently throws those away.<\/p>\n<h3>Dedupe before you average anything<\/h3>\n<p>Thirteen slugs quoted this tie. Eleven opinions came back. BetMGM and Borgata posted byte-identical prices, and so did BetParx and FourWinds, yet <code>\/v4\/bookmakers<\/code> reports <code>cloneOf: null<\/code> for all four. Average the raw list and you triple-count one trading desk.<\/p>\n<pre class=\"wp-block-code\"><code>def dedupe(board):\n    seen, unique = {}, {}\n    for slug, prices in board.items():\n        key = tuple(sorted(prices.items()))\n        if key in seen:\n            seen[key].append(slug)\n            continue\n        seen[key] = [slug]\n        unique[slug] = prices\n    clones = {v[0]: v[1:] for v in seen.values() if len(v) > 1}\n    return unique, clones\n\n\nunique, clones = dedupe(board)\nprint(f\"{len(board)} slugs -> {len(unique)} independent quotes\")\nfor keeper, dupes in clones.items():\n    print(f\"  {keeper} == {', '.join(dupes)}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>13 slugs -> 11 independent quotes\n  betmgm == borgata\n  betparx == fourwinds<\/code><\/pre>\n<h2>Step 5: De-vig Pinnacle for a fair price<\/h2>\n<p>Pinnacle priced every UCL qualifier in the sample. Strip its margin and you have a benchmark to grade the other ten books against.<\/p>\n<pre class=\"wp-block-code\"><code>def devig(prices):\n    overround = sum(1 \/ p for p in prices.values())\n    return {k: (1 \/ p) \/ overround for k, p in prices.items()}, overround - 1\n\n\nfair, vig = devig(board[\"pinnacle\"])\nprint(f\"vig {vig * 100:.2f}%\")\nfor label, prob in fair.items():\n    print(f\"  {label}: {prob * 100:.1f}%   fair price {1 \/ prob:.3f}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>vig 6.42%\n  1: 15.6%   fair price 6.407\n  X: 22.2%   fair price 4.512\n  2: 62.2%   fair price 1.607<\/code><\/pre>\n<p>That 6.42% is wide for Pinnacle. On a major-league fixture it runs closer to 2%. Qualifying ties between clubs the market barely knows carry more margin because the book is less sure, and the limit data in step 7 confirms it.<\/p>\n<h2>Step 6: Find the best available price<\/h2>\n<pre class=\"wp-block-code\"><code>def best_price(board):\n    best = {}\n    for slug, prices in board.items():\n        for label, price in prices.items():\n            if label not in best or price > best[label][1]:\n                best[label] = (slug, price)\n    return best\n\n\nfor label, (slug, price) in best_price(unique).items():\n    delta = (price \/ (1 \/ fair[label]) - 1) * 100\n    print(f\"{label}: {price:6.3f} @ {slug:12s} vs fair {1 \/ fair[label]:6.3f} ({delta:+.1f}%)\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>1:  7.143 @ kalshi       vs fair  6.407 (+11.5%)\nX:  4.545 @ kalshi       vs fair  4.512 (+0.7%)\n2:  1.562 @ polymarket   vs fair  1.607 (-2.8%)<\/code><\/pre>\n<p>Kalshi&#8217;s 7.143 on Gornik returns 30% more than SBOBet&#8217;s 5.5 for the same stake on the same outcome. That gap is the entire argument for reading more than one book.<\/p>\n<h2>Step 7: The honest read on that 11.5%<\/h2>\n<p>A price 11.5% above the sharp fair line looks like free money. Two API fields say otherwise, and both are worth checking before you stake anything.<\/p>\n<p>Kalshi is an exchange, so the outcome carries an <code>exchangeMeta<\/code> ladder instead of a single number. The top rung is thin:<\/p>\n<pre class=\"wp-block-code\"><code>payload = get(\"\/odds\", fixtureId=\"id1000000772177116\")\nquote = payload[\"bookmakerOdds\"][\"kalshi\"][\"markets\"][\"101\"][\"outcomes\"][\"101\"][\"players\"][\"0\"]\n\nfor level in quote[\"exchangeMeta\"][\"back\"]:\n    print(f\"  price {level['price']:.3f}  stake capacity ${level['limit']:,.0f}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>  price 7.143  stake capacity $914\n  price 6.667  stake capacity $384\n  price 6.250  stake capacity $3,283<\/code><\/pre>\n<p>You can get $914 down at 7.143. After that the price drops to 6.667, then 6.25. The edge is real and it is small, which is the normal shape of a genuine one.<\/p>\n<p>Pinnacle&#8217;s own <code>limit<\/code> field tells the same story from the other side. It quoted a $450 max win on Gornik and $882 on Fenerbahce. On an MLB moneyline the equivalent figure runs to $7,500. Pinnacle is holding this tie at a sixteenth of its baseball confidence, which explains the fat 6.42% margin.<\/p>\n<p>Then check whether the line has been moving. Free historical odds settle it:<\/p>\n<pre class=\"wp-block-code\"><code>history = get(\"\/historical-odds\", fixtureId=\"id1000000772177116\", bookmakers=\"pinnacle\")\nsnaps = history[\"bookmakers\"][\"pinnacle\"][\"markets\"][\"101\"][\"outcomes\"][\"101\"][\"players\"][\"0\"]\n\nprint(f\"{len(snaps)} snapshots\")\nprint(\"open \", snaps[0][\"createdAt\"], snaps[0][\"price\"])\nprint(\"last \", snaps[-1][\"createdAt\"], snaps[-1][\"price\"])<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>78 snapshots\nopen  2026-07-24T09:16:11.479Z 5.67\nlast  2026-07-29T13:09:09.238Z 6.02<\/code><\/pre>\n<p>Pinnacle opened Gornik at 5.67 and drifted to 6.02 across five days and 78 snapshots. Money has been leaving Gornik the whole time, and Kalshi sits further down that same path. A price that agrees with the direction of sharp movement is a much better bet than one that fights it. Grade every outlier this way before you trust it, because the ones that fight the drift are usually a book that has not updated.<\/p>\n<p>Note that <code>\/historical-odds<\/code> takes a maximum of three bookmakers per call and returns large payloads. Loop it with a longer pause than you use on <code>\/odds<\/code>.<\/p>\n<h2>Step 8: Corners, the market European books actually compete on<\/h2>\n<p>US sportsbooks build deep player-prop menus. European books build corner ladders, and the Champions League feed is where that shows. This single tie carried 64 distinct corner markets: full-time totals from 4.5 up to 13.5, first-half and second-half splits, per-team counts, odd\/even, a corners handicap, and a corners 1X2.<\/p>\n<pre class=\"wp-block-code\"><code>corner_lines = [m for m in CATALOG\n                if m[\"marketName\"] == \"Corners - Over Under Full Time\"]\n\npayload = get(\"\/odds\", fixtureId=\"id1000000772177116\")\nbooks = payload[\"bookmakerOdds\"]\n\nfor m in sorted(corner_lines, key=lambda m: m[\"handicap\"]):\n    quotes = {}\n    for slug, data in books.items():\n        market = data[\"markets\"].get(str(m[\"marketId\"]))\n        if not market:\n            continue\n        sides = {}\n        for outcome_id, outcome in market[\"outcomes\"].items():\n            q = outcome[\"players\"].get(\"0\")\n            if not q or q.get(\"active\") is False:\n                continue\n            sides[OUTCOME_NAME[(m[\"marketId\"], int(outcome_id))]] = q[\"price\"]\n        if sides:\n            quotes[slug] = sides\n    if quotes:\n        both = sum(1 for s in quotes.values() if len(s) == 2)\n        print(f\"corners {m['handicap']}: {len(quotes)} books ({both} two-sided)\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>corners 7.5: 6 books (5 two-sided)\ncorners 8.5: 6 books (5 two-sided)\ncorners 9.5: 9 books (8 two-sided)\ncorners 10.5: 7 books (6 two-sided)\ncorners 11.5: 6 books (5 two-sided)\ncorners 12.5: 4 books (3 two-sided)\ncorners 13.5: 2 books (1 two-sided)<\/code><\/pre>\n<p>The main line at 9.5 drew nine books including both sharps:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Book<\/th>\n<th>Over 9.5<\/th>\n<th>Under 9.5<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>pinnacle<\/td>\n<td>1.680<\/td>\n<td>2.050<\/td>\n<\/tr>\n<tr>\n<td>sbobet<\/td>\n<td>1.725<\/td>\n<td>2.020<\/td>\n<\/tr>\n<tr>\n<td>polymarket<\/td>\n<td>1.786<\/td>\n<td>2.128<\/td>\n<\/tr>\n<tr>\n<td>bet365<\/td>\n<td>1.800<\/td>\n<td>1.900<\/td>\n<\/tr>\n<tr>\n<td>betmgm<\/td>\n<td>1.880<\/td>\n<td>1.800<\/td>\n<\/tr>\n<tr>\n<td>betparx<\/td>\n<td>1.760<\/td>\n<td>1.920<\/td>\n<\/tr>\n<tr>\n<td>fanduel<\/td>\n<td>1.780<\/td>\n<td>(no price)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Count the two-sided books separately, as the snippet above does. FanDuel posted an Over and no Under on this line, and any de-vig routine that assumes both sides exist will divide by a broken overround.<\/p>\n<p>Corners also cost more than the main market. De-vigging Pinnacle&#8217;s 1.68 and 2.05 gives an 8.30% margin against 6.42% on the 1X2. Books charge for the markets fewer people shop.<\/p>\n<h2>Step 9: Correct score, the other native market<\/h2>\n<p>The same tie carried correct score priced by 14 books, and the depth varies enormously: FanDuel listed 57 scorelines, BetMGM 44, Pinnacle 20, bet365 14. Market <code>10336<\/code> holds full-time correct score, and each outcome is a scoreline label rather than a player entry.<\/p>\n<p>Depth matters here more than price. A book quoting 57 scorelines has an opinion about 4-2; a book quoting 14 is covering the obvious ones and sending everything else to a catch-all. If you are fitting a goals model, pull from the deep menus and treat the shallow ones as unusable.<\/p>\n<h2>Where this fits<\/h2>\n<p>The same eight steps work on the Europa League and the Conference League by changing one string. Conference League had 45 priced fixtures in the sample window against the Champions League&#8217;s 12, so it is the better place to test a scanner while the bigger competition is still in qualifying.<\/p>\n<p>If you want the wider soccer picture rather than the European competitions specifically, start with our <a href=\"https:\/\/oddspapi.io\/blog\/football-odds-api-soccer-data\/\">football odds API guide<\/a>. For the price-comparison logic in step 6 applied across every sport, see <a href=\"https:\/\/oddspapi.io\/blog\/line-shopping-python-best-odds\/\">line shopping in Python<\/a>. The de-vig in step 5 has two more methods worth knowing in the <a href=\"https:\/\/oddspapi.io\/blog\/no-vig-odds-api\/\">no-vig odds guide<\/a>, and <a href=\"https:\/\/oddspapi.io\/blog\/consensus-odds-fair-odds-calculator-python\/\">consensus odds<\/a> covers blending books once you have deduped them. The limit and ladder analysis in step 7 goes much deeper in <a href=\"https:\/\/oddspapi.io\/blog\/betting-limits-api-stake-sizing\/\">the betting limits guide<\/a>. Asian handicap ladders, which European books price alongside corners, are covered in <a href=\"https:\/\/oddspapi.io\/blog\/asian-handicap-api-cross-book-odds\/\">the Asian handicap API post<\/a>.<\/p>\n<h2>FAQ<\/h2>\n<h3>Is there an official UEFA Champions League odds API?<\/h3>\n<p>No. UEFA licenses data through commercial partners and does not publish odds. Bookmakers price the matches, so an aggregator that reads many books is the practical route to Champions League odds.<\/p>\n<h3>How many bookmakers price a Champions League match?<\/h3>\n<p>Between 15 and 17 in the qualifying ties measured on 29 July 2026, including Pinnacle and SBOBet. Deduping identical feeds cut 13 slugs to 11 independent quotes on the worked example, so count opinions rather than slugs.<\/p>\n<h3>Why do some Champions League fixtures have no teams?<\/h3>\n<p>The qualifying bracket is published in advance, so unresolved ties appear with participant names like &#8220;Winner Match 11&#8221;. They return <code>hasOdds: false<\/code> until the previous round finishes. Filter on that flag and on the name pattern.<\/p>\n<h3>Can I get corner odds through the API?<\/h3>\n<p>Yes. One qualifying tie carried 64 corner markets, with nine books on the main over\/under 9.5 line. Resolve them by name from <code>\/v4\/markets?sportId=10<\/code> and check for one-sided quotes before de-vigging.<\/p>\n<h3>Is historical Champions League odds data free?<\/h3>\n<p>Yes, on the free tier. The worked example returned 78 Pinnacle snapshots going back five days, enough to see the line drift from 5.67 to 6.02. Requests take up to three bookmakers each.<\/p>\n<h2>Get a key<\/h2>\n<p>Stop scraping bookmaker pages that break every time a site ships a redesign. Grab a <a href=\"https:\/\/oddspapi.io\/\">free OddsPapi key<\/a>, run the eight steps above against tonight&#8217;s qualifiers, and see what 383 books think before you back anything.<\/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 official UEFA Champions League odds API?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"No. UEFA licenses data through commercial partners and does not publish odds. Bookmakers price the matches, so an aggregator that reads many books is the practical route to Champions League odds.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How many bookmakers price a Champions League match?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Between 15 and 17 in the qualifying ties measured on 29 July 2026, including Pinnacle and SBOBet. Deduping identical feeds cut 13 slugs to 11 independent quotes on the worked example, so count opinions rather than slugs.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Why do some Champions League fixtures have no teams?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"The qualifying bracket is published in advance, so unresolved ties appear with participant names like Winner Match 11. They return hasOdds false until the previous round finishes. Filter on that flag and on the name pattern.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Can I get corner odds through the API?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes. One qualifying tie carried 64 corner markets, with nine books on the main over\/under 9.5 line. Resolve them by name from \/v4\/markets?sportId=10 and check for one-sided quotes before de-vigging.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is historical Champions League odds data free?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Yes, on the free tier. The worked example returned 78 Pinnacle snapshots going back five days, enough to see the line drift from 5.67 to 6.02. Requests take up to three bookmakers each.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<p><!--\nFocus Keyphrase: champions league odds api\nSEO Title: Champions League Odds API: Live UEFA Odds from 17 Bookmakers\nMeta Description: UEFA sells no public Champions League odds API. OddsPapi aggregates 17 books per tie including Pinnacle, plus corners and correct score. Free tier.\nSlug: champions-league-odds-api\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>UEFA sells no odds API. Read 17 books per Champions League tie in Python, including Pinnacle, corners and correct score, with free price history.<\/p>\n","protected":false},"author":2,"featured_media":3172,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[8,9,11,15,10],"class_list":["post-3171","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-free-api","tag-odds-api","tag-python","tag-soccer","tag-sports-betting-api"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Champions League Odds API: 17 Books on Every UEFA Tie | 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\/champions-league-odds-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Champions League Odds API: 17 Books on Every UEFA Tie | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"UEFA sells no odds API. Read 17 books per Champions League tie in Python, including Pinnacle, corners and correct score, with free price history.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-07T10:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-29T15:03:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-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=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"Champions League Odds API: 17 Books on Every UEFA Tie\",\"datePublished\":\"2026-08-07T10:00:00+00:00\",\"dateModified\":\"2026-08-29T15:03:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\"},\"wordCount\":1559,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp\",\"keywords\":[\"Free API\",\"Odds API\",\"Python\",\"Soccer\",\"Sports Betting API\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\",\"name\":\"Champions League Odds API: 17 Books on Every UEFA Tie | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp\",\"datePublished\":\"2026-08-07T10:00:00+00:00\",\"dateModified\":\"2026-08-29T15:03:04+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp\",\"width\":2560,\"height\":1429,\"caption\":\"Champions League Odds API - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Champions League Odds API: 17 Books on Every UEFA Tie\"}]},{\"@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":"Champions League Odds API: 17 Books on Every UEFA Tie | 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\/champions-league-odds-api\/","og_locale":"en_US","og_type":"article","og_title":"Champions League Odds API: 17 Books on Every UEFA Tie | OddsPapi Blog","og_description":"UEFA sells no odds API. Read 17 books per Champions League tie in Python, including Pinnacle, corners and correct score, with free price history.","og_url":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-08-07T10:00:00+00:00","article_modified_time":"2026-08-29T15:03:04+00:00","og_image":[{"width":2560,"height":1429,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-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":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"Champions League Odds API: 17 Books on Every UEFA Tie","datePublished":"2026-08-07T10:00:00+00:00","dateModified":"2026-08-29T15:03:04+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/"},"wordCount":1559,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp","keywords":["Free API","Odds API","Python","Soccer","Sports Betting API"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/","url":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/","name":"Champions League Odds API: 17 Books on Every UEFA Tie | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp","datePublished":"2026-08-07T10:00:00+00:00","dateModified":"2026-08-29T15:03:04+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/07\/champions-league-odds-api-scaled.webp","width":2560,"height":1429,"caption":"Champions League Odds API - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/champions-league-odds-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"Champions League Odds API: 17 Books on Every UEFA Tie"}]},{"@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\/3171","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=3171"}],"version-history":[{"count":3,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3171\/revisions"}],"predecessor-version":[{"id":3856,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3171\/revisions\/3856"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/3172"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=3171"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=3171"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=3171"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}