Understanding odds & probability

A field-by-field walkthrough of the /v1/events/{id}/intelligence probability payload, using a real MLB event. For the full field reference and type table, see API Reference → Get bet intelligence.

Definition: vig / de-vig. "Vig" (short for vigorish, a.k.a. "the juice") is the built-in margin a book bakes into a market so the implied probabilities of every outcome add up to more than 100%. Two sides both priced at -110 each imply ~52.4% — ~104.8% combined; that extra ~4.8% is the vig. "De-vig" is the reverse: normalizing those implied probabilities back down so they sum to exactly 100%, recovering the book's true underlying estimate. Every probability and fair.probability field in this payload is already de-vigged for you.

Every term on this page refers back to one real bet object — the Minnesota Twins moneyline, from a live GET /v1/events/17386/intelligence response (MLB, Twins vs. Orioles):

json
{
  "bet_type": "ML_P1",
  "player_name": "Minnesota Twins",
  "probability": 0.50236,
  "fair_price": -101,
  "market": { "price": -105, "book": "pinnacle" },
  "edge": null,
  "tier": null,
  "fair": {
    "probability": 0.50699,
    "books": ["circa", "pinnacle"],
    "n_books": 2,
    "is_consensus": true
  },
  "edges_by_book": {
    "fanduel": -0.00551,
    "hardrock": -0.01016,
    "draftkings": -0.01471,
    "caesars": -0.03626
  },
  "best": { "book": "fanduel", "price": -104, "edge": -0.00551 }
}

Two notions of "fair"

Every bet on the probability surface carries two independent fair-price estimates, and mixing them up is the single most common integration mistake:

  • Single-book de-vigmarket is the raw quoted line from one specific book, and it's an object with two distinct parts, not two names for the same thing: market.book is which bookmaker quoted it (e.g. "pinnacle"), and market.price is what odds they quoted (e.g. -105). probability is what you get by de-vigging that single book's price against itself — no other book is involved. fair_price is just probability converted back into American-odds format, so you can compare it directly to market.price.
  • fair — a separate, independently-computed sharp-consensus fair probability, sourced from the sharpest book(s) available (Pinnacle alone, or Pinnacle+Circa on MLB when both quote). This is the reference every retail book gets compared against in edges_by_book.

These two numbers are usually close but rarely identical, because they can be sourced from different books entirely. Take the Twins ML from the response above:

FieldValueSource
probability0.50236De-vig of market.book alone (here, Pinnacle at -105)
fair.probability0.50699Sharp consensus across fair.books: ["circa", "pinnacle"]

Use probability/fair_price when you want "what does the fair price look like for the book I'm quoting." Use fair as the anchor for cross-book comparison — that's exactly what edges_by_book does for you.

The fair_price arithmetic

fair_price is just probability converted to American odds — the standard vig-free conversion:

text
if probability >= 0.5:
    fair_price = -100 * probability / (1 - probability)
else:
    fair_price =  100 * (1 - probability) / probability

Plugging in the Twins moneyline (probability = 0.50236, which is ≥ 0.5):

text
-100 * 0.50236 / (1 - 0.50236) = -100 * 0.50236 / 0.49764 ≈ -101

That matches the fair_price: -101 in the response. Compare it to market.price: -105 — the book is charging 4 cents of vig on this side relative to its own de-vigged number. That gap (market.price vs. fair_price) is the book's built-in margin, not an edge you can act on — you'd need to compare against fair for that, which is what edges_by_book does next.

edges_by_book & best — line-shopping

edges_by_book answers a narrower question than "should I bet this": given the sharp-consensus fair.probability, how much of a price gap does each retail book's current line represent? The formula:

text
decimal_odds = american_to_decimal(book_price)
edge_for_book = fair.probability * decimal_odds - 1

For the Twins ML, best picked fanduel at -104 with edge: -0.00551. Checking the math: -104 American → decimal odds 1 + 100/104 ≈ 1.9615. 0.50699 * 1.9615 - 1 ≈ -0.0054 — matches (small rounding from the displayed fair.probability).

A negative edge means that book's price is worse than sharp-fair for this side (you'd be laying more than the consensus says the outcome is worth); a positive edge means it's priced better. In this example every book is slightly negative — Pinnacle/Circa were tight that day, so no retail book actually beat the sharp line. best just reports whichever book had the smallest gap (i.e. the least-bad price to shop at), not a recommendation to bet it:

BookEdge (price gap vs. fair)
fanduel-0.00551best
hardrock-0.01016
draftkings-0.01471
caesars-0.03626

best.quote_age_seconds tells you how stale that book's quote is — a large gap on a quote that's minutes old is far less meaningful than the same gap on a quote from seconds ago.

ev (Beta) — same gap, display packaging

best.edge / edges_by_book stay price gaps. bets[].ev is the one field that re-expresses a positive moneyline gap as an EV% and a full-Kelly stake so agents can display it without inventing the arithmetic. It is not a claim-ladder +EV badge and not a pick — has_recommend stays false.

On this Twins moneyline, every retail gap is negative, so ev is null. A positive FanDuel gap of 0.031 at +155 would package as:

json
{
  "beta": true,
  "book": "fanduel",
  "price": 155,
  "ev_pct": 3.1,
  "kelly_fraction": 0.02,
  "quote_age_seconds": 240.0,
  "n_books": 1
}

Gates: moneyline tokens only; a published fair (n=1 Pinnacle is enough — soccer and tennis have no second sharp book); gap > 0 and ≤ 25%; suppressed MLB books (DraftKings / FanDuel proven-null, plus unscreened BetMGM / Bet365 / BetOnline) walk to the next eligible book. Scan a sport with GET /v1/intelligence/ev (MCP list_ev, Playground #list_ev).

match_overview & rationale — context, not a pick

These two top-level, event-level fields are a separate concern from odds entirely. match_overview is a short natural-language preview of the matchup. rationale is a list of factual matchup chips (e.g. starting pitchers, injuries, recent form). Both are deliberately not a recommendation surface — no edge, tier, or pick implied — and either can be null when not yet available for an event.

These are distinct from the legacy bets[].rationale array on points-model sports, which is a list of signal-derived bullets per bet, not event-level context.

Full annotated example

The full, real GET /v1/events/17386/intelligence response this page's examples are drawn from, with the fields discussed above called out inline on the first bet:

json
{
  "event_id": 17386,
  "available": true,
  "odds_source": "pinnacle",
  "sport": "mlb",
  "league": "mlb",
  "players": {
    "home": { "name": "Minnesota Twins", "player_id": null, "team_id": 72 },
    "away": { "name": "Baltimore Orioles", "player_id": null, "team_id": 63 }
  },
  "has_recommend": false,
  "match_overview": "The Minnesota Twins (59-61) host the Baltimore Orioles (57-62) on August 12, 2026, at Target Field in a series rubber match. Both teams are below .500 and competing in the AL Central and AL East respectively. The game is scheduled for 1:40 p.m. ET with an over/under set at 8.5.", // "match_overview & rationale"
  "rationale": [                                     // "match_overview & rationale"
    "Twins RHP Zebby Matthews (5-8, 5.23 ERA, 73 SO) opposes Orioles RHP Shane Baz (4-11, 3.76 ERA, 120 SO); Matthews' team is 8-7 ATS in his starts while Baz's starts yield 10-12 ATS for Baltimore.",
    "Minnesota is 32-12 in games when not allowing a home run; Orioles are 4-6 in their last 10 games with a .219 batting average.",
    "Twins are 31-28 at home; Orioles are 26-32 as moneyline underdogs this season.",
    "Series context shows Baltimore leads the season head-to-head 7-3 in wins, 4-6 in over/under outcomes."
  ],
  "intelligence_updated_at": "2026-08-12T14:42:19Z",
  "bets": [
    {
      "bet_type": "ML_P1",
      "player_role": "home",
      "player_id": null,
      "team_id": 72,
      "player_name": "Minnesota Twins",
      "probability": 0.50236,               // de-vig of market.book (Pinnacle) alone — "Two notions of fair"
      "interval": [0.42902, 0.57569],
      "fair_price": -101,                   // -100 * .50236 / (1-.50236) ≈ -101 — "fair_price arithmetic"
      "market": { "price": -105, "line": null, "book": "pinnacle" },
      "edge": null,
      "tier": null,
      "fair": {
        "probability": 0.50699,             // sharp consensus — a different source than "probability" above
        "books": ["circa", "pinnacle"],
        "n_books": 2,
        "is_consensus": true
      },
      "edges_by_book": {                              // price gap vs fair.probability, per retail book — "edges_by_book & best"
        "caesars": -0.03625661385006673,
        "fanduel": -0.005513797120894615,
        "hardrock": -0.01015658985142276,
        "draftkings": -0.014711782719110666
      },
      "best": {                                       // smallest gap — least-bad, not a pick
        "book": "fanduel",
        "price": -104,
        "edge": -0.00551,
        "quote_age_seconds": 717.102774
      },
      "ev": null,                              // Beta — null here because every gap is ≤ 0; moneyline only
      "computed_at": "2026-08-12T14:42:19Z"
    },
    {
      "bet_type": "ML_P2",
      "player_role": "away",
      "player_id": null,
      "team_id": 63,
      "player_name": "Baltimore Orioles",
      "probability": 0.49764,
      "interval": [0.42431, 0.57098],
      "fair_price": 101,
      "market": { "price": -103, "line": null, "book": "pinnacle" },
      "edge": null,
      "tier": null,
      "fair": {
        "probability": 0.49301,
        "books": ["circa", "pinnacle"],
        "n_books": 2,
        "is_consensus": true
      },
      "edges_by_book": {
        "caesars": -0.009055862009059834,
        "fanduel": -0.03294774134064382,
        "hardrock": -0.037462457767625024,
        "draftkings": -0.018867190107979992
      },
      "best": { "book": "caesars", "price": 101, "edge": -0.00906, "quote_age_seconds": 717.106364 },
      "computed_at": "2026-08-12T14:42:19Z"
    },
    {
      "bet_type": "OVER",
      "player_role": null,
      "player_id": null,
      "team_id": null,
      "player_name": null,
      "probability": 0.52598,
      "interval": [0.44609, 0.60588],
      "fair_price": -111,
      "market": { "price": -118, "line": 8.5, "book": "pinnacle" },
      "edge": null,
      "tier": null,
      "fair": {
        "probability": 0.513,
        "books": ["circa", "pinnacle"],
        "n_books": 2,
        "is_consensus": true
      },
      "edges_by_book": {
        "betmgm": 0.02600147666367647,
        "caesars": 0.02600147666367647,
        "draftkings": 0.03113148404699473
      },
      "best": { "book": "draftkings", "price": 101, "edge": 0.03113, "quote_age_seconds": 717.111229 },
      "computed_at": "2026-08-12T14:42:19Z"
    },
    {
      "bet_type": "UNDER",
      "player_role": null,
      "player_id": null,
      "team_id": null,
      "player_name": null,
      "probability": 0.47402,
      "interval": [0.39412, 0.55391],
      "fair_price": 111,
      "market": { "price": 105, "line": 8.5, "book": "pinnacle" },
      "edge": null,
      "tier": null,
      "fair": {
        "probability": 0.487,
        "books": ["circa", "pinnacle"],
        "n_books": 2,
        "is_consensus": true
      },
      "edges_by_book": {
        "betmgm": -0.1071680202750368,
        "caesars": -0.1071680202750368,
        "draftkings": -0.11382101565301717
      },
      "best": { "book": "caesars", "price": -120, "edge": -0.10717, "quote_age_seconds": 717.106364 },
      "computed_at": "2026-08-12T14:42:19Z"
    }
  ]
}

For the complete field reference (types, nullability, every bet token per sport), see API Reference → Get bet intelligence, or open this endpoint live in the Playground.