How to Build an Arbitrage Betting Bot with Python (Free API)

Arbitrage Betting Bot - OddsPapi API Blog
How To Guides March 17, 2026

What Is Arbitrage Betting?

Arbitrage betting (“arbing”) is the practice of betting on every possible outcome of an event across different bookmakers, where the combined implied probability is less than 100%. The result: guaranteed profit regardless of who wins.

Here is a quick example. A soccer match has three outcomes (Home, Draw, Away):

Outcome Bookmaker Odds Implied Prob
Home Win Pinnacle 2.55 39.2%
Draw Bet365 3.80 26.3%
Away Win 1xBet 3.20 31.3%

Total implied probability: 96.8%. That is under 100%, which means you can stake proportionally on all three outcomes and lock in a 3.2% profit no matter what happens.

The catch? To find arbs, you need odds from as many bookmakers as possible. The more books you cover, the more arbs you find. That is why most arb scanners fail — they only check 20–40 soft bookmakers.

Why Most Arbitrage Bots Fail

Before we build anything, let us talk about why most DIY arb bots produce zero results.

1. Not Enough Bookmakers

The Odds API covers roughly 40 soft bookmakers. Arbs between soft books are razor-thin and vanish in seconds. Real, actionable arbs live in the spread between sharp bookmakers (Pinnacle, Singbet, SBOBet) and softs (Bet365, DraftKings, FanDuel). If your API does not carry sharps, you are scanning a puddle instead of an ocean.

2. No Sharp Bookmakers

Pinnacle, Singbet, and SBOBet set the “true line.” When a soft bookmaker is slow to adjust, the gap between their price and the sharp line creates an arb. Most odds APIs do not carry these books at all. OddsPapi carries all three, plus 350+ total bookmakers including crypto and niche books like 1xBet and GG.BET.

3. Polling Latency

If you poll every 30 seconds, the arb is gone before your code even prints it. The best arbs last seconds, not minutes. This is where WebSocket streaming changes the game — OddsPapi pushes odds changes to you in real-time instead of waiting for you to ask.

Building an Arb Bot: The Odds API vs OddsPapi

Feature The Odds API OddsPapi
Bookmakers ~40 (soft only) 350+ (sharps + softs)
Sharp Books (Pinnacle, Singbet) No Yes
Crypto/Niche Books No Yes (1xBet, GG.BET)
Real-Time WebSocket No Yes
Free Historical Data No Yes
Free Tier 500 req/month 250 req/month
Arb Detection Potential Low (limited coverage) High (sharp + soft spread)

The math is simple: more bookmakers = more price discrepancies = more arbs. With 350+ books including sharps, OddsPapi gives your scanner the coverage it needs to actually find opportunities.

Build an Arbitrage Scanner in Python

Let us build a working arb scanner from scratch. This code is tested against the live OddsPapi API and ready to run.

Step 1: Install Dependencies & Set Up

import requests
from datetime import datetime, timedelta

API_KEY = "YOUR_API_KEY"  # Get free at oddspapi.io
BASE_URL = "https://api.oddspapi.io/v4"

No extra libraries needed. Just requests and the standard library. Grab your free API key and drop it in.

Step 2: Get Today’s Fixtures

def get_fixtures(sport_id=10):
    """Fetch today's fixtures. Sport 10 = Soccer."""
    today = datetime.now().strftime("%Y-%m-%d")
    tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")

    response = requests.get(f"{BASE_URL}/fixtures", params={
        "apiKey": API_KEY,
        "sportId": sport_id,
        "from": today,
        "to": tomorrow
    })

    fixtures = response.json()
    # Filter to fixtures with odds available
    return [f for f in fixtures if f.get("hasOdds")]


fixtures = get_fixtures()
print(f"Found {len(fixtures)} fixtures with odds")
for f in fixtures[:5]:
    print(f"  {f['participant1Name']} vs {f['participant2Name']} ({f['tournamentName']})")

The /fixtures endpoint returns a flat list of fixtures. We filter on hasOdds so we only scan matches that actually have pricing data from bookmakers.

Step 3: Fetch Odds from All Bookmakers

def get_odds(fixture_id):
    """Get odds from all bookmakers for a fixture."""
    response = requests.get(f"{BASE_URL}/odds", params={
        "apiKey": API_KEY,
        "fixtureId": fixture_id
    })
    return response.json()

One call, every bookmaker. The response contains a bookmakerOdds dictionary keyed by bookmaker slug (e.g., pinnacle, bet365, singbet). No pagination, no extra calls.

Step 4: Find the Best Price for Each Outcome

def find_best_prices(odds_data, market_id="101"):
    """Find the best price for each outcome across all bookmakers.

    Market 101 = Full Time Result (1X2)
    Outcomes: 101=Home, 102=Draw, 103=Away
    """
    best = {}  # {outcome_id: {"price": float, "bookmaker": str}}

    for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
        market = bookie.get("markets", {}).get(market_id)
        if not market:
            continue

        for outcome_id, outcome in market.get("outcomes", {}).items():
            for player_id, player in outcome.get("players", {}).items():
                price = player.get("price")
                if price and (outcome_id not in best or price > best[outcome_id]["price"]):
                    best[outcome_id] = {
                        "price": price,
                        "bookmaker": slug,
                        "outcome_id": outcome_id
                    }

    return best

This is the core of the scanner. For each outcome in a market, we loop through every bookmaker and keep track of whoever is offering the highest price. The OddsPapi odds structure is nested: bookmakerOdds → slug → markets → marketId → outcomes → outcomeId → players → 0 → price.

Step 5: Calculate the Arbitrage Percentage

def check_arb(best_prices):
    """Check if an arbitrage opportunity exists.

    If the sum of implied probabilities < 1.0, it's an arb.
    Profit margin = (1 - sum) * 100
    """
    if not best_prices:
        return None

    total_implied = sum(1 / bp["price"] for bp in best_prices.values())
    margin = (1 - total_implied) * 100

    return {
        "is_arb": total_implied < 1.0,
        "margin": margin,
        "total_implied": total_implied,
        "prices": best_prices
    }

The formula is straightforward. Convert each best price to its implied probability (1 / odds), sum them up. If the total is under 1.0 (100%), you have an arb. The margin tells you how much guaranteed profit you can extract.

Step 6: Scan All Fixtures

def scan_for_arbs(sport_id=10, market_id="101"):
    """Scan all fixtures for arbitrage opportunities."""
    fixtures = get_fixtures(sport_id)
    print(f"Scanning {len(fixtures)} fixtures for arbs...\n")

    arbs_found = []

    for fixture in fixtures:
        fid = fixture["fixtureId"]
        name = f"{fixture['participant1Name']} vs {fixture['participant2Name']}"

        odds_data = get_odds(fid)
        best_prices = find_best_prices(odds_data, market_id)
        result = check_arb(best_prices)

        if result and result["is_arb"]:
            arbs_found.append({"fixture": name, **result})
            print(f"ARB FOUND: {name}")
            print(f"   Margin: {result['margin']:.2f}%")
            for oid, bp in result["prices"].items():
                print(f"   Outcome {oid}: {bp['price']} @ {bp['bookmaker']}")
            print()
        else:
            overround = -result["margin"] if result else 0
            bookmakers = len(odds_data.get("bookmakerOdds", {}))
            print(f"  {name}: {bookmakers} books, {overround:.1f}% overround")

    print(f"\n{'='*50}")
    print(f"Scanned {len(fixtures)} fixtures")
    print(f"Arbs found: {len(arbs_found)}")

    return arbs_found


# Run the scanner
arbs = scan_for_arbs()

Run this and watch it scan every fixture. With 350+ bookmakers feeding prices, you will see arbs that scanners limited to 40 soft books will never detect.

Taking It Further: Multi-Market & Real-Time Scanning

The scanner above covers the 1X2 (Full Time Result) market. But arbs hide in other markets too. Here is how to expand it.

Scan Multiple Markets

Loop through several markets to multiply your opportunities. Each market has its own set of outcomes and its own set of bookmaker pricing inefficiencies.

MARKETS = {
    "101": "Full Time Result (1X2)",
    "1010": "Over/Under 2.5 Goals",
    "104": "Both Teams to Score"
}

for market_id, market_name in MARKETS.items():
    print(f"\n--- {market_name} ---")
    arbs = scan_for_arbs(market_id=market_id)

Over/Under and BTTS markets often have more arbs than 1X2 because they are two-outcome markets — fewer outcomes means the margin between bookmakers needs to be smaller to create an arb, and it happens more often.

Calculate Optimal Stakes

Once you find an arb, you need to know exactly how much to bet on each outcome to guarantee profit.

def calculate_stakes(best_prices, total_stake=100):
    """Calculate how much to bet on each outcome for guaranteed profit."""
    total_implied = sum(1 / bp["price"] for bp in best_prices.values())

    stakes = {}
    for outcome_id, bp in best_prices.items():
        implied = 1 / bp["price"]
        stake = (implied / total_implied) * total_stake
        payout = stake * bp["price"]
        stakes[outcome_id] = {
            "bookmaker": bp["bookmaker"],
            "price": bp["price"],
            "stake": round(stake, 2),
            "payout": round(payout, 2)
        }

    profit = (1 / total_implied - 1) * total_stake

    return {"stakes": stakes, "profit": round(profit, 2), "roi": round((1/total_implied - 1) * 100, 2)}

The function distributes your total stake proportionally across outcomes so that every outcome pays out the same amount. The difference between total payout and total stake is your guaranteed profit.

Upgrade to WebSocket for Real-Time Detection

Polling the REST API every 30 seconds works for finding arbs, but most of them will have closed by the time you place your bets. The real edge comes from real-time WebSocket streaming. OddsPapi pushes odds changes to your bot the instant they happen, cutting your detection latency from 30 seconds to milliseconds.

WebSocket access requires a Pro tier plan. For the full setup guide, check out our WebSocket Odds API tutorial.

FAQ: Arbitrage Betting Bots

Is arbitrage betting legal?

Yes, arbitrage betting is legal in most jurisdictions. You are simply placing bets at different bookmakers. However, bookmakers do not like arbers and may limit or close your accounts if they detect consistent arb activity. Using multiple accounts and varying your bet sizes can help extend account longevity.

How many bookmakers do I need for arbs?

The more bookmakers your scanner covers, the more arbs you will find. With 40 soft-only books (like The Odds API provides), you will find close to zero actionable arbs. With 350+ books including sharps like Pinnacle and Singbet, you will find several opportunities daily. The sharp-to-soft spread is where the real arbs live.

What sports have the most arbitrage opportunities?

Soccer has the most arbs because it has the widest bookmaker coverage globally. Tennis is also strong because it is a two-outcome market (no draw), which makes arbs more common. Niche markets like esports and regional leagues also produce arbs because fewer bookmakers price them efficiently.

Do I need a paid API plan?

The free tier (250 requests/month) is enough to build and test your scanner. For production scanning across multiple sports and markets, you will want a paid plan for higher rate limits. For real-time WebSocket alerts, you need the Pro tier.

How long do arbitrage opportunities last?

Most arbs last seconds to a few minutes. Sharp bookmakers like Pinnacle move fast, and soft books adjust their lines accordingly. This is why polling-based scanners miss most arbs. Real-time WebSocket detection gives you the speed advantage you need.

Can I use this with Pinnacle?

Yes. OddsPapi aggregates odds from Pinnacle, Singbet, SBOBet, and other sharp bookmakers that most APIs do not carry. You do not need a Pinnacle commercial account or API key — OddsPapi handles the data aggregation for you.

Stop Scraping. Start Scanning.

Every minute you spend scraping individual bookmaker sites is a minute you are not finding arbs. OddsPapi gives you 350+ bookmakers — including sharps like Pinnacle and Singbet — through one API call. Free historical data lets you backtest your strategy before risking real money. And when you are ready for production, WebSocket streaming gives you the sub-second latency that separates profitable arbers from the ones who are always a step behind.

Get Your Free API Key →