Skip to main content

Overview

SharedServices provides integrated API clients for external betting data providers. These clients handle authentication, rate limiting, data transformation, and error handling.

Available Clients

FotMob

Live match data, player stats, and fixture details

Oddschecker

Odds comparison and market data scraping

Overtime

Decentralized betting markets on Optimism

Betfair

Exchange liquidity and market depth

FotMob API Client

FotMob is the primary data source for fixture data, player stats, and match results. It replaced SportMonks API.

Features

  • Cloudflare Bypass: Uses turnstile cookies for authentication
  • HTML Scraping: Fallback to HTML parsing when API is blocked
  • Circuit Breaker: Prevents hammering API when cookies expire
  • Auto Cookie Refresh: Attempts to refresh expired cookies
  • Rate Limiting: Built-in request throttling

Initialization

SharedServices/api/fotmob_service.py
from PROPPR.SharedServices.api import get_fotmob_service

# Get singleton instance
fotmob = get_fotmob_service()

# Or create new instance
from PROPPR.SharedServices.api.fotmob_service import FotMobAPIService
fotmob = FotMobAPIService(ccode3='GBR')
# Search for teams, players, or matches
results = fotmob.search("Liverpool")

print(results['squads'])  # Teams
print(results['players'])  # Players
print(results['matches'])  # Matches

Get Match Details

# Fetch match/fixture details
match_data = fotmob.get_match(match_id=4193490)

if match_data:
    general = match_data.get('general', {})
    home_team = general.get('homeTeam', {}).get('name')
    away_team = general.get('awayTeam', {}).get('name')
    
    print(f"{home_team} vs {away_team}")

Check Fixture Status

# Check if fixture is finished
is_finished = fotmob.is_fixture_finished(match_data)

if is_finished:
    print("Match has ended")

Get Player Stats

# Extract player stats from match
player_stats = fotmob.get_player_stats(
    match_data=match_data,
    player_name="Mohamed Salah"
)

if player_stats:
    print(f"Goals: {player_stats.get('goals', 0)}")
    print(f"Assists: {player_stats.get('assists', 0)}")
    print(f"Shots: {player_stats.get('shots', 0)}")
    print(f"Shots on target: {player_stats.get('shots_on_target', 0)}")

Get Team Stats

# Extract team stats from match
team_stats = fotmob.get_team_stats(
    match_data=match_data,
    team_name="Liverpool"
)

if team_stats:
    stats = team_stats.get('stats', {})
    print(f"Goals: {stats.get('goals', 0)}")
    print(f"Corners: {stats.get('corners', 0)}")
    print(f"Shots: {stats.get('shots', 0)}")
    print(f"Yellow cards: {stats.get('yellow_cards', 0)}")

Get Team Squad

# Fetch team squad/roster
squad = fotmob.get_team_squad(team_id=8650)

for player in squad:
    print(f"{player['name']} - {player['position']}")
FotMob uses Cloudflare Turnstile for protection. The client manages cookies automatically:
# Cookies are loaded from JSON file
# Path resolution order:
# 1. PROPPR config path (production/development)
# 2. /opt/proppr/turnstile_cookies.json
# 3. /opt/Shared_Services/turnstile_cookies.json (legacy)
# 4. ./turnstile_cookies.json (same directory)

# Manual cookie refresh
success = fotmob._refresh_turnstile_cookies()

if success:
    print("Cookies refreshed successfully")

Circuit Breaker

The client uses a circuit breaker to prevent hammering the API:
# Circuit breaker settings
CIRCUIT_BREAKER_THRESHOLD = 5  # Open after 5 consecutive 403s
CIRCUIT_BREAKER_COOLDOWN = 300  # Wait 5 minutes

# When circuit opens:
# - API calls are skipped
# - Logs indicate circuit is open
# - After cooldown, circuit closes automatically

Oddschecker API Client

Oddschecker provides odds comparison data via web scraping.

Features

  • Undetected ChromeDriver: Bypasses Cloudflare protection
  • Network Interception: Captures API responses via CDP
  • Parallel Processing: Multiple browser instances (configurable)
  • Market Expansion: Auto-clicks collapsed markets
  • Popup Handling: Dismisses cookie banners and ads

Initialization

SharedServices/api/oddschecker_api_client.py
from PROPPR.SharedServices.api.oddschecker_api_client import OddsCheckerAPIClient

client = OddsCheckerAPIClient(headless=True)

Scrape League Fixtures

# Scrape fixtures from a league page
fixtures = client.scrape_league_fixtures(
    league_path='football/english/premier-league'
)

for fixture in fixtures:
    print(f"{fixture['home_team']} vs {fixture['away_team']}")
    print(f"Start: {fixture['start_time']}")
    print(f"URL: {fixture['fixture_url']}")

Extract Market Data

# Extract markets from a fixture page
api_data, market_id_map = client.extract_market_ids_from_fixture(
    fixture_url='https://www.oddschecker.com/football/liverpool-v-chelsea/winner'
)

print(f"Captured {len(api_data)} markets")

for market in api_data:
    market_name = market.get('marketTypeName')
    market_id = market.get('marketId')
    print(f"{market_name} (ID: {market_id})")
    
    # Extract odds
    for runner in market.get('runners', []):
        runner_name = runner.get('runnerName')
        odds = runner.get('odds', {}).get('decimal')
        print(f"  {runner_name}: {odds}")

Process Single League

# Process a league completely (scrape + extract)
league = {
    'name': 'Premier League',
    'url': 'football/english/premier-league',
    'priority': 1,
    'country': 'England'
}

fixtures_with_markets = client.process_single_league(
    league=league,
    max_workers=1  # Sequential processing (most stable)
)

for fixture in fixtures_with_markets:
    print(f"{fixture['home_team']} vs {fixture['away_team']}")
    print(f"Markets: {len(fixture['api_data'])}")

Configuration

# From oddschecker_config.py
ODDSCHECKER_HEADLESS = True
ODDSCHECKER_PAGE_TIMEOUT = 30000  # 30 seconds
ODDSCHECKER_API_WAIT_TIMEOUT = 30000
ODDSCHECKER_MAX_RETRIES = 3
ODDSCHECKER_RETRY_DELAY = 5
ODDSCHECKER_MAX_DAYS_AHEAD = 7
ODDSCHECKER_BATCH_SIZE = 10

Overtime API Client

Overtime provides decentralized betting markets on Optimism blockchain.

Features

  • Response Hash Caching: Minimizes API calls
  • Rate Limiting: Per-league request throttling
  • Sport Filtering: Automatic soccer league detection
  • Quote API: Get betting quotes with collateral support

Initialization

SharedServices/api/overtime_api_client.py
from PROPPR.SharedServices.api.overtime_api_client import OvertimeAPIClient

client = OvertimeAPIClient(
    api_key='your_api_key',
    network_id=10  # Optimism
)

Fetch Markets

# Fetch all soccer markets
markets, response_hash = client.fetch_all_markets(
    use_cache=True,
    sport='Soccer',
    status='open'
)

if markets:
    print(f"Fetched {len(markets)} markets")
    
    for market in markets[:5]:
        print(f"{market['homeTeam']} vs {market['awayTeam']}")
        print(f"League: {market['leagueName']}")
        print(f"Date: {market['maturityDate']}")
elif markets is None:
    print("No change since last request (cache hit)")

Get Single Game

# Fetch specific game by ID
game = client.fetch_single_game(game_id='0x1234...')

if game:
    print(f"{game['homeTeam']} vs {game['awayTeam']}")
    print(f"Odds: {game['odds']}")

Get Quote

# Get quote for a bet
trade_data = [
    {
        'gameId': '0x1234...',
        'position': 0,  # 0 = home, 1 = draw, 2 = away
        'odds': 2.5
    }
]

quote = client.get_quote(
    buy_in=10.0,
    trade_data=trade_data,
    collateral='USDC'  # USDC, USDT, OVER, ETH
)

if quote:
    print(f"Payout: {quote['payout']}")
    print(f"Total quote: {quote['totalQuote']}")

Get User History

# Fetch user's betting history
history = client.get_user_history(wallet_address='0xabc...')

if history:
    print(f"Open bets: {len(history['open'])}")
    print(f"Claimable: {len(history['claimable'])}")
    print(f"Closed: {len(history['closed'])}")

Statistics

# Get client stats
stats = client.get_stats()

print(f"Total requests: {stats['total_requests']}")
print(f"Cache hits: {stats['cache_hits']}")
print(f"Cache misses: {stats['cache_misses']}")
print(f"API errors: {stats['api_errors']}")
print(f"Rate limit waits: {stats['rate_limit_waits']}")

# Reset stats
client.reset_stats()

# Clear cache
client.clear_cache()

Betfair Exchange Client

Betfair provides exchange liquidity data.
Source: SharedServices/api/betfair_exchange_liquidity.pyThe Betfair client is implemented but not heavily used in current production. It provides exchange market data and liquidity information for advanced betting analysis.

Best Practices

All clients have built-in rate limiting. Respect the limits to avoid API bans.
Enable response caching (Overtime, FotMob) to reduce API calls.
FotMob uses circuit breakers. Don’t force API calls when circuit is open.
For FotMob, ensure turnstile cookies are fresh. Implement auto-refresh.
For Oddschecker, use headless=True in production for stability.

Error Handling

FotMob

try:
    match_data = fotmob.get_match(12345)
    if match_data is None:
        # API blocked or circuit open
        logger.warning("FotMob API unavailable")
except Exception as e:
    logger.error(f"FotMob error: {e}")

Oddschecker

try:
    fixtures = client.scrape_league_fixtures('football/english/premier-league')
except Exception as e:
    if "target window already closed" in str(e):
        # Browser crashed
        logger.error("Browser crash - retry")
    else:
        logger.error(f"Oddschecker error: {e}")

Overtime

try:
    markets, _ = client.fetch_all_markets()
    if markets is None:
        # No change (cache hit) or error
        pass
except requests.HTTPError as e:
    if e.response.status_code == 401:
        logger.error("Invalid API key")
    elif e.response.status_code == 429:
        logger.error("Rate limit exceeded")

Next Steps

Bet Grading

See how API clients are used in grading

Market Mapping

Learn about stat type mapping

Build docs developers (and LLMs) love