> ## Documentation Index
> Fetch the complete documentation index at: https://docs.odds-api.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Arbitrage Bets

> Find prematch arbitrage opportunities across your bookmakers with Odds-API.io: margins after exchange fees, stakes that respect bookmaker limits, filters by sport, league, market and minimum profit.

## What are Arbitrage Bets?

An arbitrage (or "arb") is a price gap across bookmakers: the best odds for every outcome of a market, taken from different bookmakers, imply a total probability below 100%.

The `/v3/arbitrage-bets` endpoint returns these price gaps for the bookmakers you choose, with the calculated margin and a calculated stake split per leg that gives an equal modelled return at the quoted prices.

## How Arbitrage Bets Work

For every prematch market line (for example `Totals 2.5` or `Spread -1.5`), we compare the best prices across bookmakers on each side:

1. Take the best prices for every outcome of the line
2. Add up the implied probabilities (`1 / odds`) of one price per outcome
3. Keep the combinations where the total is below 1

**Formula:**

```
impliedProbability = 1/odds(outcome 1) + 1/odds(outcome 2) [+ 1/odds(outcome 3)]
profitMargin = (1 - impliedProbability) / impliedProbability × 100
```

Some details that affect the numbers:

* **Exchange fees are included.** On exchanges and prediction markets, the price you keep after commission or taker fees is lower than the quoted price. Those legs carry `netOdds`, and `profitMargin`, `impliedProbability` and `optimalStakes` are all computed on `netOdds`.
* **Stakes respect bookmaker limits.** When a bookmaker reports a maximum stake for a price, the leg carries `maxStake` and the whole split is scaled down so no leg goes over it. `totalStake` is 1000 by default and can be lower when a limit caps it.
* **Prematch only.** Opportunities are refreshed every few seconds and removed as soon as a price moves or the match starts.

## Query Parameters

| Parameter | Required | Description |
| - | - | - |
| `apiKey` | Yes | Your API key |
| `bookmakers` | Yes | Comma-separated bookmaker names (e.g. `Bet365,SingBet`). Every leg of a returned opportunity uses one of these bookmakers. |
| `sport` | No | Filter by sport name or slug (e.g. `Football` or `football`) |
| `league` | No | Filter by league slug (e.g. `england-premier-league`). Requires `sport` to also be set. |
| `markets` | No | Comma-separated market names to keep, case-insensitive (e.g. `ML,Totals`). Max 40. |
| `minProfit` | No | Minimum `profitMargin` in percent, between 0 and 100 (e.g. `1.5` for 1.5%) |
| `allCombinations` | No | `true` returns every bookmaker combination per event, market and line. Default `false`: only the best one. |
| `limit` | No | Maximum number of results (default 50, max 500) |
| `includeEventDetails` | No | Include match info (home, away, league, sport, date) in the response |

## Fetching Arbitrage Bets

```javascript JavaScript theme={null}
const apiKey = process.env.ODDS_API_KEY;
const params = new URLSearchParams({
  apiKey,
  bookmakers: 'Bet365,SingBet,Betano',
  includeEventDetails: 'true'
});

const response = await fetch(`https://api.odds-api.io/v3/arbitrage-bets?${params}`);
const arbs = await response.json();
console.log(arbs);
```

```python Python theme={null}
import requests
import os

response = requests.get(
    'https://api.odds-api.io/v3/arbitrage-bets',
    params={
        'apiKey': os.environ['ODDS_API_KEY'],
        'bookmakers': 'Bet365,SingBet,Betano',
        'includeEventDetails': 'true'
    }
)

arbs = response.json()
print(arbs)
```

```php PHP theme={null}
<?php
$url = 'https://api.odds-api.io/v3/arbitrage-bets?' . http_build_query([
    'apiKey' => getenv('ODDS_API_KEY'),
    'bookmakers' => 'Bet365,SingBet,Betano',
    'includeEventDetails' => 'true'
]);

$arbs = json_decode(file_get_contents($url), true);
print_r($arbs);
```

## Filtering

Filters run on our side, so only the opportunities you care about come back.

```bash Football only theme={null}
curl "https://api.odds-api.io/v3/arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet&sport=football"
```

```bash Premier League totals theme={null}
curl "https://api.odds-api.io/v3/arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet&sport=football&league=england-premier-league&markets=Totals"
```

```bash At least 1.5% profit theme={null}
curl "https://api.odds-api.io/v3/arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet&minProfit=1.5"
```

Validation rules:

* Providing `league` without `sport` returns `400`.
* Unknown `sport` slug returns `400`.
* Unknown `league` for a valid sport returns `404`.
* `minProfit` outside 0 to 100, or not a number, returns `400`.

## One Combination per Line, or All of Them

The same line often arbs with more than one pair (or trio) of bookmakers. By default you get one row per event, market and line: the combination with the highest `profitMargin` among those that use only your bookmakers. Adding a bookmaker to `bookmakers` can therefore change which combination you see for a line.

Set `allCombinations=true` to get every combination for each line, best first. This is useful when one of your accounts is limited at a bookmaker and you want the next best way to cover the same line.

```bash theme={null}
curl "https://api.odds-api.io/v3/arbitrage-bets?apiKey=YOUR_KEY&bookmakers=Bet365,SingBet,Betano&allCombinations=true"
```

## Response Format

```json theme={null}
[
  {
    "id": "123456-Totals-2.50-Betfair Exchange-over-Bet365-under",
    "eventId": 123456,
    "profitMargin": 3.79,
    "totalStake": 1000,
    "impliedProbability": 0.9635,
    "market": {
      "name": "Totals",
      "hdp": 2.5
    },
    "legs": [
      {
        "bookmaker": "Betfair Exchange",
        "side": "over",
        "odds": "2.16",
        "netOdds": "2.102",
        "maxStake": 850,
        "oddsUpdatedAt": "2026-10-21T09:40:12Z",
        "href": "https://www.betfair.com/exchange/..."
      },
      {
        "bookmaker": "Bet365",
        "side": "under",
        "odds": "2.05",
        "oddsUpdatedAt": "2026-10-21T09:39:58Z",
        "href": "https://www.bet365.com/..."
      }
    ],
    "optimalStakes": [
      {"bookmaker": "Betfair Exchange", "side": "over", "stake": 493.73, "potentialReturn": 1037.82},
      {"bookmaker": "Bet365", "side": "under", "stake": 506.27, "potentialReturn": 1037.85}
    ],
    "updatedAt": "2026-10-21T09:40:15Z",
    "firstSeenAt": "2026-10-21T09:31:02Z",
    "event": {
      "home": "Manchester United",
      "away": "Liverpool",
      "date": "2026-10-25T15:00:00Z",
      "sport": "Football",
      "league": "England - Premier League"
    }
  }
]
```

## Understanding the Response

| Field | Description |
| - | - |
| `id` | `{eventId}-{market}[-{hdp}]` followed by `{bookmaker}-{side}` for each leg. Stable while the same combination stays open. |
| `profitMargin` | Margin at the quoted prices after exchange fees, as a percentage of `totalStake` (3.79 means 3.79%) |
| `totalStake` | 1000, or less when a leg's `maxStake` caps the split. Scale `optimalStakes` to your own bankroll. |
| `impliedProbability` | Sum of `1/odds` across the legs, using `netOdds` where fees apply. Below 1 for an arbitrage. |
| `market.hdp` | The line for Spread and Totals markets; omitted when the market has no line |
| `legs[].odds` | Quoted decimal price, as a string |
| `legs[].netOdds` | Price after exchange fees, as a string. Only present when fees apply. |
| `legs[].maxStake` | Stake limit the bookmaker reports for this price. Only present when known. |
| `legs[].oddsUpdatedAt` | Last time we wrote this bookmaker price. Some bookmakers are only written when the price changes, so a price can be old and still correct. May be absent. |
| `legs[].href` | Link to the event at the bookmaker, or `"N/A"` when none is known |
| `optimalStakes[]` | Stake per leg for an equal payout. `potentialReturn` is the same for every leg, up to rounding. |
| `updatedAt` | Last time the opportunity was confirmed |
| `firstSeenAt` | When this combination first appeared |
| `event` | Match details (when `includeEventDetails=true`) |

## Scaling Stakes to Your Bankroll

`optimalStakes` are calculated for `totalStake`. To stake a different amount, multiply every leg by the same factor, and keep each leg under its `maxStake`:

```javascript theme={null}
function scaleStakes(arb, bankroll) {
  let factor = bankroll / arb.totalStake;
  for (const stake of arb.optimalStakes) {
    const leg = arb.legs.find(l => l.bookmaker === stake.bookmaker && l.side === stake.side);
    if (leg?.maxStake) factor = Math.min(factor, leg.maxStake / stake.stake);
  }
  return arb.optimalStakes.map(s => ({
    ...s,
    stake: +(s.stake * factor).toFixed(2),
    potentialReturn: +(s.potentialReturn * factor).toFixed(2)
  }));
}
```

## Polling for New Opportunities

Opportunities are recalculated every few seconds, but match your polling interval to your rate limit rather than to our refresh rate. Use `id` to spot new combinations and `firstSeenAt` to see how long one has been open:

```javascript theme={null}
const seen = new Set();

async function poll() {
  const params = new URLSearchParams({
    apiKey: process.env.ODDS_API_KEY,
    bookmakers: 'Bet365,SingBet,Betano',
    minProfit: '1',
    includeEventDetails: 'true'
  });
  const arbs = await (await fetch(`https://api.odds-api.io/v3/arbitrage-bets?${params}`)).json();

  for (const arb of arbs) {
    if (seen.has(arb.id)) continue;
    seen.add(arb.id);
    console.log(`${arb.event.home} vs ${arb.event.away}: ${arb.market.name} ${arb.market.hdp ?? ''} at ${arb.profitMargin}%`);
  }
}

await poll();
setInterval(poll, 60000);
```

## Limitations & Considerations

> [!WARNING]
>
> * Margins and stakes are calculations at the quoted prices, not guaranteed returns. Prices can move at any time.
> * `maxStake` is the limit the bookmaker reports; your own account may be limited lower.
> * Bookmakers can void or resettle bets on obvious price errors, so unusually high margins deserve a second look.
> * Bookmakers do not always settle a market the same way (for example overtime rules).

## Next Steps

> **[Value Bets](/guides/value-bets)**
>
> Prices above a fair-odds estimate

> **[Best Practices](/guides/best-practices)**
>
> Optimize your API usage


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.