# Sports Odds Arbitrage & Value Bet Finder (`normdata/sports-odds-arbitrage-scraper`) Actor

Compare live and pre-match odds across Pinnacle, Bovada, BetRivers and Unibet. Best price per outcome, Pinnacle fair odds, surebets with stake split, +EV value bets. NFL, NBA, MLB, NHL, soccer, tennis, UFC, boxing. No proxy.

- **URL**: https://apify.com/normdata/sports-odds-arbitrage-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Categories:** Sports, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 games

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

![Norm Data](https://raw.githubusercontent.com/FabriAV/normdata/refs/heads/main/assets/banner_norm2.png)

## 💰 Sports Odds Arbitrage & Value Bet Finder

Compare live and pre-match odds across **Pinnacle, Bovada, BetRivers and Unibet** in one run. Every row is one game, matched across the books, with the **best price for every outcome**, **Pinnacle fair odds** with the margin taken out, every **arbitrage (surebet)** with the stake split, and every **value bet** priced above fair odds. Moneylines, 1X2, spreads and totals for NFL, NBA, MLB, NHL, soccer, tennis, UFC and boxing. About **1,900 matched games in under 15 seconds**, no proxy.

### 🎯 Who uses it?

#### 💸 Arbitrage bettors

Find surebets across four books in seconds, with the profit and the exact stake on each leg already worked out.

#### 📈 Value bettors

Spot prices that beat Pinnacle's no-vig fair odds, the sharpest reference in the market, and bet only where you have an edge.

#### 🤖 Model builders and quants

Feed a model with clean, matched odds from a sharp book and three recreational books, and record closing lines over time.

#### 📰 Odds comparison sites and tipsters

Show the best line per outcome next to your previews, or alert your audience the moment an arbitrage opens.

### ✨ What it does

- **Four books, matched game by game:** Pinnacle (the sharp reference), Bovada, BetRivers and Unibet. Team and player names are matched across books even when they're spelled differently ("Man Utd" and "Manchester United", "LA Clippers" and "Los Angeles Clippers").
- **8 sports:** American football, basketball, baseball, ice hockey, soccer, tennis, MMA and boxing.
- **Best price per outcome:** home, draw and away as columns, with the book that pays the most.
- **Fair odds:** Pinnacle's price with its margin removed, for every market Pinnacle prices.
- **Arbitrage finder:** every market where backing all outcomes at the best books guarantees a profit, with profit %, each leg's book and odds, and the share of your bankroll to stake on each.
- **Value bet finder:** every price at least N% above fair odds, with the edge.
- **Every market by book:** moneyline (2-way and 3-way), spread and total, main and alternate lines, with each book's decimal and American odds and the market margin.
- **Correct periods:** soccer 1X2 is 90 minutes, hockey 3-way is regulation time, the rest include overtime. Markets are only compared when they settle the same way.
- **Live or upcoming:** games in play, upcoming games, or both, plus games starting within the next N hours.
- **Filters:** sports, leagues, teams or players, market types, minimum books per game, only arbitrage, only value bets, sort by start time, arbitrage profit or value edge.
- **Direct links** to the game page at every book.

### 🏆 Why this scraper

- **Arbitrage and value done for you.** Not just raw odds: the matching, margins, fair odds, surebets and stakes are computed on every row.
- **A sharp reference built in.** Pinnacle's no-vig line is the industry's benchmark for what a price is really worth.
- **Fast.** All four books, all eight sports, about 1,900 games matched in under 15 seconds in our tests, with no browser and no proxy.
- **Careful about false positives.** Live spreads and totals are not compared (books settle them differently in play), and stale or impossible arbitrages are dropped.
- **Pay per game, not per book.** One row holds every book's prices for that game.

### ⚖️ How it compares

| Capability | This actor | Single-book odds scrapers | Odds API aggregators |
|---|:--:|:--:|:--:|
| Pinnacle, Bovada, BetRivers, Unibet in one row | **yes** | one book | some of them |
| Games matched across books | **yes** | no | yes |
| Arbitrage with stake split | **yes** | no | paid add-on, if any |
| Value bets vs Pinnacle fair odds | **yes** | no | rarely |
| Alternate lines compared | **yes** | varies | rarely |
| Live and upcoming | **yes** | varies | yes |
| Price | **$0.02 per game, every book included** | per event per book | monthly plan |
| Proxy | **none needed** | varies | n/a |

### 📦 What data you get

| Field | Description |
| --- | --- |
| `gameId` | Stable ID: sport, date and both teams, e.g. `basketball-2026-10-03-lakers-vs-celtics`. |
| `sport`, `league` | Sport and league or tournament. |
| `eventName`, `homeTeam`, `awayTeam` | Game and teams or players. |
| `startTime`, `status` | Start time (UTC, ISO 8601) and `upcoming` or `live`. |
| `bookmakerCount`, `bookmakers` | The books that price this game. |
| `bestHomeOdds`, `bestDrawOdds`, `bestAwayOdds` + `...Bookmaker` | Best moneyline price per outcome and where (1X2 for soccer). |
| `fairHomeOdds`, `fairDrawOdds`, `fairAwayOdds` | Pinnacle's no-vig moneyline. |
| `hasArbitrage`, `bestArbitrageProfitPercent`, `arbitrageCount` | Whether any market on this game is an arbitrage, and the best profit. |
| `arbitrages` | Each arbitrage: market, period, line, profit %, and every leg with outcome, book, odds and stake %. |
| `bestValueEdgePercent`, `valueBetCount`, `valueBets` | Each value bet: market, outcome, book, odds, fair odds and edge %. |
| `marketCount`, `markets` | Every market: type, period, line, number of books, margin, and every outcome with the best price, fair odds, edge and each book's odds. |
| `links` | The game page at each book. |
| `scrapedAt` | When the odds were read. |

Example row (trimmed):

```json
{
  "gameId": "tennis-2026-10-03-ashton-bowers-vs-astra-sharma",
  "sport": "Tennis",
  "league": "ITF Women Nashville - SF",
  "eventName": "Ashton Bowers vs Astra Sharma",
  "startTime": "2026-10-03T18:00:00.000Z",
  "status": "upcoming",
  "bookmakerCount": 3,
  "bookmakers": ["Pinnacle", "BetRivers", "Unibet"],
  "bestHomeOdds": 5.99,
  "bestHomeBookmaker": "Pinnacle",
  "bestAwayOdds": 1.22,
  "bestAwayBookmaker": "BetRivers",
  "fairHomeOdds": 6.367,
  "fairAwayOdds": 1.186,
  "hasArbitrage": true,
  "bestArbitrageProfitPercent": 1.36,
  "arbitrages": [
    {
      "market": "moneyline",
      "period": "Match",
      "line": null,
      "profitPercent": 1.36,
      "legs": [
        { "outcome": "home", "name": "Ashton Bowers", "bookmaker": "Pinnacle", "odds": 5.99, "oddsAmerican": "+499", "stakePercent": 16.92 },
        { "outcome": "away", "name": "Astra Sharma", "bookmaker": "BetRivers", "odds": 1.22, "oddsAmerican": "-455", "stakePercent": 83.08 }
      ]
    }
  ],
  "bestValueEdgePercent": 2.87,
  "valueBets": [
    { "market": "moneyline", "period": "Match", "outcome": "away", "name": "Astra Sharma", "bookmaker": "BetRivers", "odds": 1.22, "fairOdds": 1.186, "edgePercent": 2.87 }
  ],
  "marketCount": 1,
  "markets": [
    {
      "market": "moneyline",
      "period": "Match",
      "line": null,
      "bookmakerCount": 3,
      "margin": -1.34,
      "isArbitrage": true,
      "outcomes": [
        {
          "outcome": "home",
          "name": "Ashton Bowers",
          "bestOdds": 5.99,
          "bestBookmaker": "Pinnacle",
          "prices": [
            { "bookmaker": "Pinnacle", "odds": 5.99, "oddsAmerican": "+499" },
            { "bookmaker": "BetRivers", "odds": 3.85, "oddsAmerican": "+285" },
            { "bookmaker": "Unibet", "odds": 3.85, "oddsAmerican": "+285" }
          ],
          "fairOdds": 6.367,
          "edgePercent": -5.92
        }
      ]
    }
  ],
  "links": [{ "bookmaker": "Pinnacle", "url": "https://www.pinnacle.com/en/tennis/..." }],
  "scrapedAt": "2026-10-03T12:41:10.512Z"
}
```

A stake split reads like this: with $1,000, put $169.20 on Bowers at Pinnacle and $830.80 on Sharma at BetRivers. Either way you get back about $1,013.60.

### 💡 Use cases

#### 💸 Every arbitrage right now, best first

```json
{ "onlyArbitrage": true, "sortBy": "arbitrage" }
```

#### 📈 Value bets of 3% or more on the NFL

```json
{ "leagues": ["NFL"], "onlyValueBets": true, "minValueEdge": 3, "sortBy": "value" }
```

#### ⚽ Best 1X2 prices for Premier League games

```json
{ "maxItems": 50, "sports": ["soccer"], "leagues": ["Premier League"], "markets": ["moneyline_3way"] }
```

#### 🔴 Live moneylines on games all four books price

```json
{ "eventStatus": "live", "markets": ["moneyline", "moneyline_3way"], "minBookmakers": 4 }
```

#### 🏀 Main totals only, NBA and NFL, next 24 hours

```json
{ "sports": ["basketball", "american_football"], "markets": ["total"], "alternateLines": false, "startsWithinHours": 24 }
```

#### 🗺️ BetRivers Pennsylvania and Unibet Sweden links

```json
{ "maxItems": 200, "betriversState": "pa", "unibetCountry": "se" }
```

### ⚙️ How the input is organised

| Section | What it's for |
| --- | --- |
| **Maximum games** | Always first, caps the whole run. Empty means no limit. |
| **Bookmakers** | Which books to compare, your BetRivers state and Unibet country, and the minimum number of books per game. |
| **What to compare** | Sports, leagues or countries, teams or players, live or upcoming, how soon games start. |
| **Markets** | Moneyline, 1X2, spread, total, and whether to include alternate lines. Empty means all. |
| **Arbitrage and value bets** | Keep only games with an arbitrage or a value bet, minimum profit or edge, and the sort order. |

### 💰 Pricing

Pay-per-event: a one-time **$0.003** when the run starts, plus a price per game saved to your dataset, with every book and every market included. The price depends on your Apify plan tier:

| Your Apify plan | Price per game |
| --- | --- |
| Free | $0.028 |
| Bronze | $0.025 |
| Silver | $0.022 |
| Gold / Platinum / Diamond | $0.02 |

Approximate total cost (includes the $0.003 start fee):

| Games collected | Free | Bronze | Silver | Gold+ |
| --- | --- | --- | --- | --- |
| 100 | $2.80 | $2.50 | $2.20 | $2.00 |
| 1,000 | $28.00 | $25.00 | $22.00 | $20.00 |
| 10,000 | $280.00 | $250.00 | $220.00 | $200.00 |

Tip: "Only games with an arbitrage" and "Only games with a value bet" keep your dataset to the games that matter, so you pay only for those. New Apify accounts start with $5 in free credit. On the Free plan a run saves up to 10 games.

### 🚀 Run it

1. Create a free Apify account with $5 in credit.
2. Open the Sports Odds Arbitrage & Value Bet Finder.
3. Keep the four books, pick sports or leagues if you want, set Maximum games, then click **Start**.
4. Open the **Arbitrage** or **Value bets** view in the Dataset tab, or export everything as CSV, Excel, JSON or XML.

Run it programmatically through the Apify API (`run-sync-get-dataset-items`) or the ApifyClient for JavaScript and Python, and schedule it every few minutes with Apify's built-in Scheduler to catch arbitrages as they open.

### 🤖 Use with AI agents (MCP)

Give an AI agent live access to cross-book odds, arbitrages and value bets through the Model Context Protocol:

```
claude mcp add --transport http apify "https://mcp.apify.com?tools=normdata/sports-odds-arbitrage-scraper"
```

Then prompt it in plain language, e.g. "find today's NBA value bets of 3% or more".

### 🔧 Troubleshooting

**No arbitrages found.**
That's normal for a while: arbitrages are rare and close quickly. Add sports, include live games, or schedule the actor every few minutes.

**A game is missing a book.**
Books don't all list the same games: Bovada and BetRivers focus on US sports, Unibet on European soccer, and smaller tennis and soccer leagues are often on one or two books only. Lower "Minimum bookmakers per game" to see those too.

**Fewer games than expected.**
Games priced by fewer books than your minimum are left out, and the log says how many. A league or team filter that matches nothing returns nothing; use the name the books show ("NFL", "Premier League", "Chiefs").

**Live spreads and totals have no arbitrage.**
By design: in play, Pinnacle settles spreads and totals on the rest of the match while other books count the whole game, so only live moneylines are compared.

**`fairHomeOdds` is empty.**
Pinnacle doesn't price that game or market, so there is no fair line and no value bets on it. Arbitrages are still found.

**A "sportsbook asked to slow down" line in the log.**
The run waits a few seconds and carries on. Nothing is lost.

### ❓ FAQ

| Question | Answer |
| --- | --- |
| Do I need accounts at these books? | No. Only public odds are read. |
| Does it need a proxy? | No. |
| Is there a limit on results? | No. Leave Maximum games empty to get every game your books share. |
| How fresh are the odds? | Read live on every run. Schedule it every few minutes to track movement. |
| Why Pinnacle for fair odds? | It takes the sharpest action and has the lowest margins, so its no-vig line is the market's best estimate of the true probability. |
| Do BetRivers odds differ by state? | In our tests every state had the same prices. The state picks which games are offered and where the links go. The same holds for Unibet countries. |
| Are player props included? | No. This actor compares moneylines, 1X2, spreads and totals, the markets every book offers. |
| Is an arbitrage guaranteed? | The math is, the prices aren't: odds move and books limit stakes. Check every leg before you bet. |
| Can I place bets with it? | No. It only reads odds. |

### 🛡️ Limits & responsible use

This Actor reads only public odds that the books show to any visitor. It never signs in and never places bets. Odds change quickly, some books void bets placed on obvious errors, and stake limits apply; always check each price at the book before acting. Use the data in line with the books' terms and the gambling laws where you are. Please gamble responsibly.

### 📧 Contact

Need a scraper for a different site, or found something wrong with this one? norm.data.scrapers@gmail.com

### 🧪 Local development

```powershell
bun install
bun run typecheck
bun test/qa.mjs
apify run
```

Local results are stored in `storage/datasets/default`.

# Actor input Schema

## `maxItems` (type: `integer`):

Stop after this many games. Leave empty for no limit (every game the books share). The Apify Free plan is always capped at 10.

## `bookmakers` (type: `array`):

The books whose odds are compared side by side. Pinnacle is the sharp reference used for fair odds and value bets, so keep it in. BetRivers and Unibet are priced on Kambi but set their own odds, so both are worth comparing.

## `betriversState` (type: `string`):

Where you bet at BetRivers. Odds are the same in every state; the state sets which games are offered (some states ban college or in-state teams) and where the links point.

## `unibetCountry` (type: `string`):

Where you bet at Unibet. Odds are the same in every country; the country sets the game menu and where the links point.

## `minBookmakers` (type: `integer`):

Only games priced by at least this many of your books. 2 is the least that lets you compare.

## `sports` (type: `array`):

Only these sports. Leave empty for all.

## `leagues` (type: `array`):

Only games whose league name (at any of the books) contains one of these, e.g. "NFL", "NBA", "Premier League", "ATP", "England". Leave empty for every league.

## `teams` (type: `array`):

Only games with one of these teams or players, e.g. "Chiefs", "Real Madrid", "Sinner". Whole words; accents and case do not matter.

## `eventStatus` (type: `string`):

Upcoming games, games in play right now, or both. Live prices move by the second, so live arbitrage closes fast.

## `startsWithinHours` (type: `integer`):

Only games starting in the next N hours, e.g. 24 for the next day. Live games are always included. Leave empty for every scheduled game.

## `markets` (type: `array`):

Which markets to compare. Leave empty for all: moneyline (2-way), 1X2 (3-way, soccer and hockey regulation time), spread (handicap, puck line, run line) and total (over/under).

## `alternateLines` (type: `boolean`):

Also compare Pinnacle alternate spreads and totals wherever another book prices the same line. More lines, more chances to find an edge.

## `onlyArbitrage` (type: `boolean`):

Keep only games where backing every outcome at the best price locks in a profit (surebets).

## `minArbitrageProfit` (type: `number`):

Ignore arbitrages paying less than this, e.g. 1 for 1% or more.

## `onlyValueBets` (type: `boolean`):

Keep only games where a book pays more than the fair odds (Pinnacle price with its margin removed), i.e. positive expected value (+EV).

## `minValueEdge` (type: `number`):

How far above the fair odds a price must be to count as a value bet, e.g. 2 for 2% or more.

## `sortBy` (type: `string`):

Order of the rows.

## Actor input object example

```json
{
  "maxItems": 10,
  "bookmakers": [
    "pinnacle",
    "bovada",
    "betrivers",
    "unibet"
  ],
  "betriversState": "nj",
  "unibetCountry": "uk",
  "minBookmakers": 2,
  "leagues": [
    "NFL",
    "NBA",
    "Premier League"
  ],
  "teams": [
    "Chiefs",
    "Lakers",
    "Real Madrid"
  ],
  "eventStatus": "all",
  "alternateLines": true,
  "onlyArbitrage": false,
  "minArbitrageProfit": 0,
  "onlyValueBets": false,
  "minValueEdge": 2,
  "sortBy": "startTime"
}
```

# Actor output Schema

## `games` (type: `string`):

One row per game: best price per outcome, fair odds, arbitrages, value bets and every market by book.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "maxItems": 10,
    "bookmakers": [
        "pinnacle",
        "bovada",
        "betrivers",
        "unibet"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/sports-odds-arbitrage-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "maxItems": 10,
    "bookmakers": [
        "pinnacle",
        "bovada",
        "betrivers",
        "unibet",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/sports-odds-arbitrage-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "maxItems": 10,
  "bookmakers": [
    "pinnacle",
    "bovada",
    "betrivers",
    "unibet"
  ]
}' |
apify call normdata/sports-odds-arbitrage-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,normdata/sports-odds-arbitrage-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/05fljYnu2H9B3RHQQ/builds/hFHWL4HGD2DJBteGv/openapi.json
