Tutorials

UK Player Props API: Compare Goalscorer Odds Across All Bookmakers (Python)

UK Player Props API: Compare Goalscorer Odds Across All Bookmakers (Python)

Player prop markets — anytime goalscorer, player shots, player cards, player assists — are the fastest-growing segment of UK football betting. Every major bookmaker now offers them, and the pricing varies wildly between books. The problem? Getting player prop odds programmatically from UK bookmakers has been nearly impossible.

Most odds APIs either don't carry player props at all (Sportmonks — 42 markets, no props), limit them to US sports (The Odds API), or only have them for a single bookmaker (Odds-API.io confirmed they only have player props for Bet365 from UK books). Nobody covers player props across Bet365, Sky Bet, Paddy Power, William Hill, Ladbrokes, Coral, Betfred, and the rest of the UK high street.

UKOddsApi does. This tutorial shows you how to pull player prop odds from all 25+ UK bookmakers and build tools that compare goalscorer, shots, cards, and assists prices across every bookmaker simultaneously.

What player prop markets are available

UKOddsApi provides player-level and team-level prop markets through the package=full parameter. Use the markets endpoint to discover what's available:

import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.ukoddsapi.com"
headers = {"X-Api-Key": API_KEY}

# Discover all available market types
response = requests.get(
    f"{BASE_URL}/v1/football/markets",
    headers=headers,
    params={"package": "full"}
)

markets = response.json()["markets"]

# Group by category
groups = {}
for m in markets:
    g = m.get("market_group", "other")
    if g not in groups:
        groups[g] = []
    groups[g].append(m["market_name"])

for group, market_list in sorted(groups.items()):
    print(f"\n📂 {group.upper()}")
    for name in market_list:
        print(f"   • {name}")

Example output:

📂 MAIN
   • Win Market
   • Both Teams to Score
   • Over/Under 2.5 Goals
   • Double Chance
   • Draw No Bet

📂 GOALS
   • Anytime Goalscorer
   • First Goalscorer
   • Last Goalscorer
   • Player to Score 2+

📂 SHOTS
   • Player Shots Over/Under
   • Player Shots on Target Over/Under

📂 CARDS
   • Player to be Carded
   • Player to be Shown Red Card

📂 ASSISTS
   • Player Assists Over/Under

📂 CORNERS
   • Total Corners Over/Under
   • Total Home Corners
   • Total Away Corners

📂 BOOKINGS
   • Total Match Booking Points
   • Team Booking Points

Step 1: Pull goalscorer odds from all UK bookmakers

from datetime import date

# Get today's fixtures
events = requests.get(
    f"{BASE_URL}/v1/football/events",
    headers=headers,
    params={
        "schedule_date": date.today().isoformat(),
        "league": "premier-league",
    }
).json()["events"]

event_id = events[0]["event_id"]

# Get player props with package=full
response = requests.get(
    f"{BASE_URL}/v1/football/events/{event_id}/odds",
    headers=headers,
    params={
        "package": "full",
        "market": "goalscorer",  # Filter to goalscorer markets
        "odds_format": "decimal",
    }
)

odds = response.json()
print(f"⚽ {odds['event_title']}")
print(f"   {odds['summary']['bookmakers_count']} bookmakers providing player props\n")

for market in odds["markets"]:
    print(f"📈 {market['market_name']}\n")

    # Group by player
    players = {}
    for sel in market["selections"]:
        pname = sel["selection_name"]
        if pname not in players:
            players[pname] = []
        players[pname].append({
            "bookmaker": sel["bookmaker_name"],
            "code": sel["bookmaker_code"],
            "odds": sel["odds"],
        })

    # Sort players by shortest price (most likely scorers first)
    sorted_players = sorted(
        players.items(),
        key=lambda x: min(o["odds"] for o in x[1])
    )

    for pname, bookies in sorted_players[:8]:
        bookies.sort(key=lambda x: -x["odds"])  # Best value first
        best = bookies[0]
        worst = bookies[-1]
        spread = round(((best["odds"] / worst["odds"]) - 1) * 100, 1)

        print(f"  🎯 {pname}")
        print(f"     Best:  {best['odds']} @ {best['bookmaker']}")
        print(f"     Worst: {worst['odds']} @ {worst['bookmaker']}")
        print(f"     Spread: {spread}% ({len(bookies)} bookmakers)")
        print()

Output:

⚽ Arsenal vs Chelsea
   22 bookmakers providing player props

📈 Anytime Goalscorer

  🎯 Kai Havertz
     Best:  2.80 @ BoyleSports
     Worst: 2.50 @ Bet365
     Spread: 12.0% (18 bookmakers)

  🎯 Bukayo Saka
     Best:  3.40 @ Coral
     Worst: 3.00 @ Bet365
     Spread: 13.3% (20 bookmakers)

  🎯 Nicolas Jackson
     Best:  3.60 @ William Hill
     Worst: 3.10 @ Sky Bet
     Spread: 16.1% (17 bookmakers)

  🎯 Cole Palmer
     Best:  4.20 @ BetVictor
     Worst: 3.60 @ Paddy Power
     Spread: 16.7% (19 bookmakers)

Notice those spreads: 12-17% price differences between UK bookmakers on the same player. That's significant value being left on the table by anyone who only checks one bookmaker.

Step 2: Build a player props value finder

This tool scans all fixtures and finds the biggest pricing discrepancies — where one bookmaker is offering substantially higher odds than the rest:

def find_prop_value(fixtures, market_filter="goalscorer", min_spread_pct=10):
    """
    Find player prop markets with the biggest bookmaker disagreements.
    Higher spread = bigger value opportunity.
    """
    opportunities = []

    for fixture in fixtures:
        response = requests.get(
            f"{BASE_URL}/v1/football/events/{fixture['event_id']}/odds",
            headers=headers,
            params={
                "package": "full",
                "market": market_filter,
                "odds_format": "decimal",
            }
        )

        if response.status_code != 200:
            continue

        odds = response.json()

        for market in odds["markets"]:
            players = {}
            for sel in market["selections"]:
                pname = sel["selection_name"]
                if pname not in players:
                    players[pname] = []
                players[pname].append({
                    "bookmaker": sel["bookmaker_name"],
                    "odds": sel["odds"],
                })

            for pname, bookies in players.items():
                if len(bookies) < 3:
                    continue

                bookies.sort(key=lambda x: -x["odds"])
                best = bookies[0]
                worst = bookies[-1]

                # Calculate average excluding best and worst
                mid_odds = [b["odds"] for b in bookies[1:-1]]
                avg_odds = sum(mid_odds) / len(mid_odds)

                spread_pct = ((best["odds"] / worst["odds"]) - 1) * 100
                vs_avg_pct = ((best["odds"] / avg_odds) - 1) * 100

                if spread_pct >= min_spread_pct:
                    opportunities.append({
                        "match": fixture["event_title"],
                        "market": market["market_name"],
                        "player": pname,
                        "best_odds": best["odds"],
                        "best_bookie": best["bookmaker"],
                        "worst_odds": worst["odds"],
                        "worst_bookie": worst["bookmaker"],
                        "avg_odds": round(avg_odds, 2),
                        "spread_pct": round(spread_pct, 1),
                        "vs_avg_pct": round(vs_avg_pct, 1),
                        "bookmaker_count": len(bookies),
                    })

    return sorted(opportunities, key=lambda x: -x["spread_pct"])


# Scan Premier League fixtures
fixtures = requests.get(
    f"{BASE_URL}/v1/football/events",
    headers=headers,
    params={"league": "premier-league"}
).json()["events"]

print("🔍 Scanning player props for value...\n")
opps = find_prop_value(fixtures, market_filter="goalscorer", min_spread_pct=15)

print(f"🎯 Found {len(opps)} opportunities with 15%+ spread\n")
print(f"{'Player':<22}{'Match':<28}{'Best':>7}{'Bookie':<16}{'Avg':>7}{'Spread':>8}")
print(f"{'-'*88}")

for opp in opps[:20]:
    print(
        f"{opp['player'][:21]:<22}"
        f"{opp['match'][:27]:<28}"
        f"{opp['best_odds']:>7.2f}"
        f"{opp['best_bookie'][:15]:<16}"
        f"{opp['avg_odds']:>7.2f}"
        f"{opp['spread_pct']:>+7.1f}%"
    )

Step 3: Compare shots and cards props

Player props go far beyond goalscorer markets. Here's how to pull shots on target and cards data:

# Player shots
response = requests.get(
    f"{BASE_URL}/v1/football/events/{event_id}/odds",
    headers=headers,
    params={
        "package": "full",
        "market": "shots",
        "odds_format": "decimal",
    }
)

shots = response.json()
print(f"🎯 Player Shots — {shots['event_title']}\n")

for market in shots["markets"]:
    print(f"  📈 {market['market_name']}")

    # Group by player
    players = {}
    for sel in market["selections"]:
        pname = sel["selection_name"]
        if pname not in players:
            players[pname] = []
        players[pname].append({
            "bookmaker": sel["bookmaker_name"],
            "odds": sel["odds"],
        })

    for pname, bookies in list(players.items())[:5]:
        best = max(bookies, key=lambda x: x["odds"])
        print(f"    {pname}: {best['odds']} @ {best['bookmaker']} (best of {len(bookies)} bookmakers)")
    print()


# Player cards
response = requests.get(
    f"{BASE_URL}/v1/football/events/{event_id}/odds",
    headers=headers,
    params={
        "package": "full",
        "market": "card",
        "odds_format": "decimal",
    }
)

cards = response.json()
print(f"🟨 Player Cards — {cards['event_title']}\n")

for market in cards["markets"]:
    print(f"  📈 {market['market_name']}")
    players = {}
    for sel in market["selections"]:
        pname = sel["selection_name"]
        if pname not in players:
            players[pname] = []
        players[pname].append({
            "bookmaker": sel["bookmaker_name"],
            "odds": sel["odds"],
        })

    for pname, bookies in sorted(players.items(), key=lambda x: min(b["odds"] for b in x[1]))[:5]:
        best = max(bookies, key=lambda x: x["odds"])
        shortest = min(bookies, key=lambda x: x["odds"])
        print(f"    {pname}: {best['odds']} (best) — {shortest['odds']} (shortest) across {len(bookies)} bookmakers")
    print()

Step 4: Full player props comparison tool

"""
player_props_scanner.py

Compare player prop odds across all UK bookmakers.
Find value on goalscorer, shots, cards, and assists markets.

Requires UKOddsApi Pro plan for player props access.
Sign up at https://ukoddsapi.com

Usage:
    pip install requests
    python player_props_scanner.py
"""

import requests
from datetime import date

# ─── Configuration ───────────────────────────────────────
API_KEY = "your_api_key_here"
BASE_URL = "https://api.ukoddsapi.com"
LEAGUE = "premier-league"
MARKET_FILTER = None  # None = all props, or "goalscorer", "shots", "card"
MIN_BOOKMAKERS = 5    # Minimum bookmakers pricing a selection
# ─────────────────────────────────────────────────────────

headers = {"X-Api-Key": API_KEY}


def get_player_props(event_id, market_filter=None):
    params = {"package": "full", "odds_format": "decimal"}
    if market_filter:
        params["market"] = market_filter

    r = requests.get(
        f"{BASE_URL}/v1/football/events/{event_id}/odds",
        headers=headers, params=params
    )
    return r.json()


def analyse_props(odds_data, min_bookmakers=5):
    results = []

    for market in odds_data["markets"]:
        # Group by selection
        selections = {}
        for sel in market["selections"]:
            sname = sel["selection_name"]
            if sname not in selections:
                selections[sname] = []
            selections[sname].append({
                "bookmaker": sel["bookmaker_name"],
                "odds": sel["odds"],
            })

        for sname, bookies in selections.items():
            if len(bookies) < min_bookmakers:
                continue

            bookies.sort(key=lambda x: -x["odds"])
            odds_values = [b["odds"] for b in bookies]
            avg = sum(odds_values) / len(odds_values)

            results.append({
                "market": market["market_name"],
                "group": market["market_group"],
                "selection": sname,
                "best_odds": bookies[0]["odds"],
                "best_bookie": bookies[0]["bookmaker"],
                "worst_odds": bookies[-1]["odds"],
                "worst_bookie": bookies[-1]["bookmaker"],
                "avg_odds": round(avg, 2),
                "spread": round(((bookies[0]["odds"] / bookies[-1]["odds"]) - 1) * 100, 1),
                "bookmaker_count": len(bookies),
                "all_bookmakers": bookies,
            })

    return sorted(results, key=lambda x: -x["spread"])


def main():
    r = requests.get(f"{BASE_URL}/v1/auth/verify", headers=headers)
    if not r.json().get("ok"):
        raise SystemExit("❌ Invalid API key. Get yours at ukoddsapi.com (Pro plan required)")

    print("=" * 70)
    print("🎯  UK PLAYER PROPS SCANNER")
    print(f"    Comparing props across 25+ UK bookmakers")
    print(f"    League: {LEAGUE}")
    print("=" * 70)

    fixtures = requests.get(
        f"{BASE_URL}/v1/football/events",
        headers=headers,
        params={"league": LEAGUE, "schedule_date": date.today().isoformat()}
    ).json().get("events", [])

    if not fixtures:
        fixtures = requests.get(
            f"{BASE_URL}/v1/football/events",
            headers=headers,
            params={"league": LEAGUE}
        ).json().get("events", [])

    print(f"\n📅 {len(fixtures)} fixtures to scan\n")

    for fixture in fixtures[:5]:
        print(f"\n{'='*70}")
        print(f"⚽ {fixture['event_title']}")
        print(f"{'='*70}")

        odds = get_player_props(fixture["event_id"], MARKET_FILTER)
        results = analyse_props(odds, MIN_BOOKMAKERS)

        if not results:
            print("   No player props available yet for this fixture")
            continue

        # Group by market type
        by_market = {}
        for r in results:
            if r["market"] not in by_market:
                by_market[r["market"]] = []
            by_market[r["market"]].append(r)

        for market_name, props in by_market.items():
            print(f"\n  📈 {market_name}")
            print(f"  {'Player/Selection':<25}{'Best':>7}{'Bookie':<16}{'Worst':>7}{'Spread':>8}{'#':>4}")
            print(f"  {'-'*67}")

            for prop in props[:8]:
                print(
                    f"  {prop['selection'][:24]:<25}"
                    f"{prop['best_odds']:>7.2f}"
                    f"{prop['best_bookie'][:15]:<16}"
                    f"{prop['worst_odds']:>7.2f}"
                    f"{prop['spread']:>+7.1f}%"
                    f"{prop['bookmaker_count']:>4}"
                )

    print(f"\n{'='*70}")
    print("💡 Higher spread = bigger value opportunity")
    print("   Pro tip: back the best odds, lay at Betfair for risk-free value")


if __name__ == "__main__":
    main()

Why this matters

A 15% spread on an anytime goalscorer market means one bookmaker is offering £2.80 while another offers £2.44 on the same player in the same match. If you're placing a £50 bet, that's the difference between a £140 return and a £122 return — £18 on a single bet, just by checking the right bookmaker.

Multiply that across every match, every player, every market type — and you see why comparing player props across all UK bookmakers is so valuable. Nobody else can build this tool because nobody else has UK player prop odds from more than one or two bookmakers.

API endpoints used

Endpoint Purpose
GET /v1/football/events/{id}/odds?package=full Player and team props
GET /v1/football/markets?package=full Discover available prop types
GET /v1/football/events Find fixtures

Use the market parameter to filter by type: goalscorer, shots, card, corner, booking.


UKOddsApi is the only API providing player prop odds across all 25+ UK bookmakers. Compare goalscorer, shots, cards, and assists prices in one call. Pro plan required. Get started at ukoddsapi.com