BettingLab

Build Discord Sports Betting Alert Bot Python API

Marcus Hale
Marcus Hale

Building a Discord sports betting alert bot Python API integration is one of the most practical ways to automate your edge detection workflow. Instead of manually checking odds across dozens of books, you can build a bot that monitors the MoneyLine API and pushes high-value opportunities directly to your Discord server.

I've been running variations of this setup for two years. The core loop is simple: fetch current edges, filter for your criteria, post alerts when something hits. The execution details matter more than you'd think.

Why Discord for Betting Alerts

Discord beats Slack, Telegram, or email for betting notifications. The mobile app is instant, channels keep alerts organized, and webhooks are dead simple to implement. You can separate MLB alerts from NBA alerts, set different thresholds per sport, and mute channels during off-seasons.

Most importantly, Discord doesn't throttle webhook frequency like some email providers. When you're scanning for arbitrage windows that close in minutes, delivery speed matters.

Setting Up Your Discord Webhook

First, create a Discord server if you don't have one. Add a dedicated channel for betting alerts โ€” I use #ev-alerts for positive expected value bets and #arbitrage for cross-book opportunities.

Navigate to your channel settings, then Integrations โ†’ Webhooks โ†’ New Webhook. Copy the webhook URL. This is your pipeline to Discord.

import requests
import json
from datetime import datetime
import time

# Your Discord webhook URL
DISCORD_WEBHOOK = "https://discord.com/api/webhooks/your-webhook-url-here"

# MoneyLine API configuration
MONEYLINE_API_BASE = "https://mlapi.bet"
MONEYLINE_API_KEY = "your-api-key"

def send_discord_alert(title, description, color=0x00ff00):
    """Send formatted alert to Discord channel"""
    embed = {
        "title": title,
        "description": description,
        "color": color,
        "timestamp": datetime.utcnow().isoformat(),
        "footer": {
            "text": "MoneyLine API Alert"
        }
    }
    
    payload = {"embeds": [embed]}
    
    response = requests.post(DISCORD_WEBHOOK, json=payload)
    if response.status_code != 204:
        print(f"Discord webhook failed: {response.status_code}")
    
    return response.status_code == 204

Test this function with a simple alert before building the full monitoring system.

Fetching High-EV Opportunities

The MoneyLine API /v1/edge endpoint returns calculated edges across all tracked sportsbooks. This is your data source for identifying profitable betting opportunities.

def fetch_current_edges(min_edge_percent=5.0, sports=["MLB", "NBA", "NHL"]):
    """Fetch current positive EV bets from MoneyLine API"""
    headers = {
        "Authorization": f"Bearer {MONEYLINE_API_KEY}",
        "Content-Type": "application/json"
    }
    
    params = {
        "min_edge": min_edge_percent,
        "sports": ",".join(sports),
        "status": "active"
    }
    
    response = requests.get(
        f"{MONEYLINE_API_BASE}/v1/edge",
        headers=headers,
        params=params
    )
    
    if response.status_code != 200:
        print(f"API error: {response.status_code}")
        return []
    
    edges = response.json()
    return edges.get("edges", [])

def format_edge_alert(edge_data):
    """Format edge data for Discord display"""
    game = edge_data["game"]
    bet = edge_data["bet"]
    sportsbook = edge_data["sportsbook"]
    
    title = f"๐ŸŽฏ {edge_data['edge_percent']:.1f}% Edge - {game['sport']}"
    
    description = f"""
**{game['away_team']} @ {game['home_team']}**
{bet['selection']} {bet['line']} at {bet['odds']}
Book: {sportsbook['name']}
Fair Odds: {edge_data['fair_odds']}
Expected Value: +{edge_data['expected_value']:.2f}%
Game Time: {game['start_time']}
"""
    
    return title.strip(), description.strip()

This pulls current edges above your minimum threshold and formats them for Discord's embed system. The embed format makes alerts scannable โ€” you can quickly spot the edge percentage, game, and bet type.

Building the Alert Loop

The monitoring loop runs continuously, checking for new edges every few minutes. You need to track which alerts you've already sent to avoid spam.

import sqlite3
from datetime import datetime, timedelta

def setup_alert_database():
    """Initialize SQLite database for tracking sent alerts"""
    conn = sqlite3.connect('betting_alerts.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS sent_alerts (
            id INTEGER PRIMARY KEY,
            edge_id TEXT UNIQUE,
            sent_at TIMESTAMP,
            edge_percent REAL,
            game_info TEXT
        )
    ''')
    
    conn.commit()
    return conn

def was_alert_sent(conn, edge_id):
    """Check if we've already sent this alert"""
    cursor = conn.cursor()
    cursor.execute('SELECT id FROM sent_alerts WHERE edge_id = ?', (edge_id,))
    return cursor.fetchone() is not None

def mark_alert_sent(conn, edge_data):
    """Record that we sent this alert"""
    cursor = conn.cursor()
    edge_id = f"{edge_data['game']['id']}_{edge_data['bet']['id']}_{edge_data['sportsbook']['id']}"
    
    cursor.execute('''
        INSERT OR REPLACE INTO sent_alerts 
        (edge_id, sent_at, edge_percent, game_info)
        VALUES (?, ?, ?, ?)
    ''', (
        edge_id,
        datetime.utcnow(),
        edge_data['edge_percent'],
        f"{edge_data['game']['away_team']} @ {edge_data['game']['home_team']}"
    ))
    
    conn.commit()

def monitoring_loop():
    """Main monitoring loop for betting alerts"""
    conn = setup_alert_database()
    
    print("Starting betting alert monitoring...")
    
    while True:
        try:
            edges = fetch_current_edges(min_edge_percent=7.0)
            print(f"Found {len(edges)} edges above 7%")
            
            for edge in edges:
                edge_id = f"{edge['game']['id']}_{edge['bet']['id']}_{edge['sportsbook']['id']}"
                
                if not was_alert_sent(conn, edge_id):
                    title, description = format_edge_alert(edge)
                    
                    if send_discord_alert(title, description):
                        mark_alert_sent(conn, edge)
                        print(f"Sent alert: {edge['edge_percent']:.1f}% edge")
                    else:
                        print("Failed to send Discord alert")
                        
        except Exception as e:
            print(f"Monitoring error: {e}")
            
        # Wait 3 minutes before next check
        time.sleep(180)

if __name__ == "__main__":
    monitoring_loop()

The SQLite database prevents duplicate alerts and gives you a history of what opportunities you've been notified about. Useful for backtesting your alert thresholds.

Advanced Filtering and Customization

Basic edge percentage filtering catches most opportunities, but you can build more sophisticated rules. Maybe you only want MLB games with specific teams, or NBA player props above certain liquidity thresholds.

def advanced_edge_filter(edge_data):
    """Apply custom filtering logic beyond basic edge percentage"""
    game = edge_data["game"]
    bet = edge_data["bet"]
    
    # Skip low-liquidity markets
    if edge_data.get("market_liquidity", 0) < 1000:
        return False
    
    # MLB: Focus on run lines and totals during day games
    if game["sport"] == "MLB":
        if bet["market"] in ["run_line", "total"] and "day" in game.get("game_type", ""):
            return True
        return False
    
    # NBA: Player props only during prime time
    if game["sport"] == "NBA":
        game_hour = datetime.fromisoformat(game["start_time"]).hour
        if bet["market"].startswith("player_") and 19 <= game_hour <= 22:
            return True
        return False
    
    # Default: accept all qualifying edges
    return True

def send_filtered_alerts():
    """Send alerts with advanced filtering applied"""
    conn = setup_alert_database()
    edges = fetch_current_edges(min_edge_percent=5.0)
    
    for edge in edges:
        if advanced_edge_filter(edge):
            edge_id = f"{edge['game']['id']}_{edge['bet']['id']}_{edge['sportsbook']['id']}"
            
            if not was_alert_sent(conn, edge_id):
                title, description = format_edge_alert(edge)
                
                # Color-code by edge size
                if edge['edge_percent'] >= 20:
                    color = 0xff0000  # Red for huge edges
                elif edge['edge_percent'] >= 10:
                    color = 0xff8800  # Orange for strong edges  
                else:
                    color = 0x00ff00  # Green for standard edges
                
                if send_discord_alert(title, description, color):
                    mark_alert_sent(conn, edge)

This filtering system lets you tune alerts for your specific betting strategy. Sharp bettors often focus on specific markets or game types where they have the most edge.

Deployment and Reliability

For production deployment, wrap the monitoring loop in proper error handling and logging. I recommend running this on a VPS rather than your local machine โ€” network interruptions will miss alerts.

import logging
import sys
from datetime import datetime

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('betting_alerts.log'),
        logging.StreamHandler(sys.stdout)
    ]
)

def robust_monitoring_loop():
    """Production monitoring loop with error recovery"""
    conn = setup_alert_database()
    consecutive_errors = 0
    max_consecutive_errors = 5
    
    logging.info("Starting production betting alert monitoring")
    
    while True:
        try:
            edges = fetch_current_edges(min_edge_percent=6.0)
            consecutive_errors = 0  # Reset error counter on success
            
            alerts_sent = 0
            for edge in edges:
                if advanced_edge_filter(edge):
                    edge_id = f"{edge['game']['id']}_{edge['bet']['id']}_{edge['sportsbook']['id']}"
                    
                    if not was_alert_sent(conn, edge_id):
                        title, description = format_edge_alert(edge)
                        
                        if send_discord_alert(title, description):
                            mark_alert_sent(conn, edge)
                            alerts_sent += 1
                            
            logging.info(f"Monitoring cycle complete. Sent {alerts_sent} new alerts.")
            
        except Exception as e:
            consecutive_errors += 1
            logging.error(f"Monitoring error ({consecutive_errors}/{max_consecutive_errors}): {e}")
            
            if consecutive_errors >= max_consecutive_errors:
                logging.critical("Too many consecutive errors. Stopping monitoring.")
                break
                
        time.sleep(180)  # 3-minute intervals
        
if __name__ == "__main__":
    robust_monitoring_loop()

Add systemd service files or Docker containers for automatic restarts. The goal is 99%+ uptime โ€” missing a 15% edge because your script crashed is expensive.

Consider adding a heartbeat mechanism that posts to a separate Discord channel every hour. This confirms the bot is running even during quiet periods with no alerts.

API Rate Limits and Optimization

The MoneyLine API free tier includes 1000 credits per month. Each /v1/edge call consumes one credit. Running alerts every 3 minutes means roughly 14,400 API calls per month โ€” you'll need a paid tier for continuous monitoring.

Optimize by combining multiple sports in single API calls and caching results for sub-minute intervals. The edge detection algorithms update every few minutes, so polling faster than that provides minimal benefit.

For high-frequency strategies like arbitrage detection, consider upgrading to the premium tier with higher rate limits and real-time websocket feeds.

Frequently Asked Questions

How often should the bot check for new edges?

Every 2-3 minutes strikes the right balance. Betting lines move quickly, but the MoneyLine API edge calculations update every few minutes. Checking more frequently burns through your API quota without finding significantly more opportunities.

What minimum edge percentage should I set for alerts?

Start with 7-8% for your first week to see typical volume. Most successful bettors filter for 5-6% edges on liquid markets and 8-10%+ edges on niche props. Lower thresholds create too much noise; higher thresholds miss solid opportunities.

Can I run multiple Discord bots for different sports?

Yes, create separate Discord channels and webhooks for each sport. Run the same Python script with different filtering parameters. This keeps alerts organized and lets you mute specific sports during off-seasons.

How do I handle API errors and downtime?

Implement exponential backoff on API failures and maintain local state between restarts. Log all errors and set up separate monitoring to alert when your bot stops posting heartbeats. Never let a crashed script cost you profitable betting opportunities.

Should I alert on all positive EV bets or just high-edge opportunities?

Alert on high-edge opportunities (7%+) for immediate action, but log all positive EV bets for analysis. This helps you understand which sportsbooks consistently offer better odds and which markets provide the most edge over time.

Build with the same data we use.

MoneyLine API powers BettingLab's edge calculations. Free tier, 1k credits/month.