Sports Odds → Fair Probability API
Pricing
from $0.01 / 1,000 dataset items
Sports Odds → Fair Probability API
Aggregate sportsbook odds via The Odds API (bring your own key) and strip the vig with multiplicative/power/Shin de-vig to get fair, vig-free probabilities.
Pricing
from $0.01 / 1,000 dataset items
Rating
0.0
(0)
Developer
Daniel Posztos
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
4 days ago
Last modified
Categories
Share
Sports Odds → Fair Probability API — The Odds API De-Vig
SEO title: Sports Odds Fair Probability API — De-Vig Odds SEO description: Aggregate sportsbook odds via The Odds API (bring your own key) and strip the vig (multiplicative/power/Shin) to get fair, vig-free probabilities. No scraping, no anti-bot.
Raw sportsbook odds are a commodity — dozens of sites publish them. The processed, vig-free probability is the product: this actor pulls moneyline/spread/total odds from The Odds API (you bring your own key — zero scraping, zero anti-bot risk) and strips the bookmaker's margin with your choice of multiplicative, power, or Shin (1992) de-vig, returning one row per (event, bookmaker, market) with raw odds, raw implied probability, and the fair de-vigged probability side by side.
Built for betting modelers, cross-market arbitrage tooling (pairs naturally with actor #7, Prediction Market Odds API), and AI agents that need a clean, structured, already-de-vigged number instead of raw decimal odds.
Why this exists
Every sportsbook's quoted odds sum to more than 100% implied probability — that extra margin is the bookmaker's vig (their guaranteed edge). A raw odds feed tells you what the book is charging; it doesn't tell you the book's actual assessment of the probability. Removing the vig ("de-vig") requires picking a model for how the margin was distributed across outcomes, and three are in common use, each with different assumptions:
- Multiplicative (aka proportional): normalizes raw implied probabilities to sum to 1. Simple, robust, the industry default.
- Power: solves for a shared exponent applied to every outcome's raw implied probability. Shrinks longshot probabilities more aggressively than multiplicative.
- Shin (1992): models the vig as compensation for insider/informed betting. Widely considered the most theoretically grounded for sports betting specifically (as opposed to prediction markets). This actor's Shin implementation is n-way (works for 2-outcome moneylines/spreads/totals AND 3-outcome soccer 1X2 markets), validated against a hand-verified 2-way reference; it falls back to multiplicative (logged) only for the rare book with ~zero/negative overround, where Shin's model has no solution.
This actor computes all three the same way for every row, so you can compare methods on the exact same odds instead of re-implementing the math yourself.
Input
| Field | Type | Default | Description |
|---|---|---|---|
odds_api_key | string (secret) | — | Required. Your own The Odds API key (free tier: 500 requests/month). Bring-your-own-key: never logged, never in output. |
sport | string | — | Required. A The Odds API sport_key, e.g. "basketball_nba", "soccer_epl", or the special value "upcoming" (next games across all sports). Validated against the live /v4/sports catalog at run time (free) — an unknown key fails fast with example valid keys. |
leagues | array of strings | [] | Additional sport_keys to fetch in the SAME run alongside sport (each costs its own separate quota). |
books | array of strings | [] | Bookmaker keys to restrict to, e.g. ["draftkings", "fanduel", "pinnacle"]. Maps to the Odds API bookmakers param. |
regions | array of strings | ["us"] | Odds API region codes (us, uk, eu, au, ...) controlling which bookmakers are eligible. Each additional region multiplies quota cost. |
markets | array of strings | ["h2h"] | "h2h" (moneyline/1x2), "spreads" (point spread), "totals" (over/under). Each requested market multiplies quota cost. |
devig_method | string | "multiplicative" | "multiplicative", "power", or "shin". |
max_events | integer (1–200) | 25 | Max DISTINCT events, fair-shared across every requested sport_key. Each event still produces one row per (bookmaker × market) it has. |
Worked example — input
{"odds_api_key": "YOUR_THE_ODDS_API_KEY","sport": "basketball_wnba","markets": ["h2h"],"devig_method": "multiplicative","max_events": 3}
Worked example — output (1 real row, live data 2026-07-06)
{"event": {"id": "1fbe348b23c9f547e63ce18ccf0888db","sport_key": "basketball_wnba","sport_title": "WNBA","commence_time": "2026-07-06T23:40:00Z","home_team": "Washington Mystics","away_team": "Golden State Valkyries"},"bookmaker": { "key": "draftkings", "title": "DraftKings" },"market": "h2h","devig_method": "multiplicative","outcomes": [{ "name": "Golden State Valkyries", "point": null, "raw_odds": 1.4, "implied_prob": 0.7142857142857143, "devigged_prob": 0.6853932584269663 },{ "name": "Washington Mystics", "point": null, "raw_odds": 3.05, "implied_prob": 0.3278688524590164, "devigged_prob": 0.3146067415730337 }],"overround": 1.0421545667447307,"devig_note": null,"last_update": "2026-07-06T21:29:09Z","fetched_at": "2026-07-06T21:29:13.328287+00:00"}
Note implied_prob sums to 1.0421... (the 4.2% overround/vig DraftKings is charging) while devigged_prob sums to exactly 1.0 — the whole point of this actor. The same market run through devig_method: "power" gave 0.6973/0.3027; through "shin", 0.6932/0.3068 — all three sum to 1.0, all three pull the favorite up and the underdog down relative to raw implied probability, by different (documented) amounts.
Output schema
One row per (event, bookmaker, market). See .actor/dataset_schema.json for the full JSON Schema.
| Field | Type | Notes |
|---|---|---|
event | object | id, sport_key, sport_title, commence_time (ISO 8601 UTC), home_team, away_team |
bookmaker | object | key (e.g. "draftkings"), title (e.g. "DraftKings") |
market | string | "h2h", "spreads", or "totals" |
devig_method | string | Which method this run used (uniform across every row) |
outcomes | array | name, point (spread/total line; null for h2h), raw_odds (decimal), implied_prob (1/raw_odds, pre-devig), devigged_prob (fair, post-devig; null if skipped — see devig_note) |
overround | number or null | Sum of implied_prob across the row's outcomes, pre-devig. null when de-vig was skipped |
devig_note | string or null | Non-null only when de-vig was skipped for this row — explains why |
last_update | string or null | When this bookmaker last updated this market, per The Odds API |
fetched_at | string | When this run fetched this row |
How spreads/totals are de-vigged
A spreads or totals market object, as returned by The Odds API's main-lines endpoint, already contains exactly the 2 complementary outcomes for one single line (e.g. -6.5/+6.5, or Over 155.5/Under 155.5) — confirmed live, never multiple alternate lines mixed into one market. So this actor de-vigs a market's own outcomes list directly, with no extra cross-line or cross-bookmaker grouping step: h2h, spreads, and totals are all handled by the exact same code path. If a market has fewer than 2 outcomes, or any outcome is missing a usable price (suspended/unavailable), de-vig is skipped for that row (devigged_prob: null on every outcome) and devig_note explains why — the row still ships with raw_odds/implied_prob wherever available, it is never silently dropped.
Pricing (pay-per-event)
| Event | Price | When it's charged |
|---|---|---|
event-row | $0.002 | Once per (event, bookmaker, market) row returned |
Plus Apify's own apify-actor-start synthetic event (first 5 seconds of compute free, platform-managed). Your own Odds API quota is separate — this actor never charges you for that, but a free-tier key is capped at 500 requests/month, and markets/regions multiply how many of those a single run costs (see "Quota notes" below).
Example: 25 events × ~6 bookmakers × 1 market averaged = ~150 rows: 150 × $0.002 = $0.30.
If a run's cost would exceed the Max total charge USD you set, the actor stops delivering further rows at exactly that point — every delivered row was paid for, nothing paid-for is ever dropped, no crash. Because ALL upstream Odds API fetching happens once per requested sport_key, up front, before any charging starts, a budget stop never leaves a half-fetched, wasted upstream request behind either.
Quota notes (The Odds API, your own key)
GET /v4/sports(used internally to validatesport/leagues) is free — confirmed live, doesn't touch your quota.- Each odds fetch costs
(number of markets requested) × (number of regions requested)of your quota, regardless ofbooksfiltering or how many events/bookmakers come back. Requestingmarkets: ["h2h", "spreads", "totals"]costs 3× a single-market request. - Each additional
leaguesentry is a separate fetch with its own cost. - Free-tier keys get 500 requests/month total (shared across every tool you use that key with, including this one).
Error messages
- Missing/invalid
odds_api_key: "Missing or invalid 'odds_api_key': provide your own The Odds API key..." — exit 1, no upstream call made at all. - Missing/invalid
sport: "Missing or invalid 'sport': provide a The Odds API sport_key..." — exit 1, no upstream call made. - Unsupported
markets/devig_method: states the exact allowed values — exit 1, no upstream call made. - Unknown
sport/leaguesvalue: "Unknown sport_key(s) [...] -- not present in The Odds API's live /v4/sports catalog..." with example valid keys — exit 1. This check itself is free (doesn't cost quota). - Invalid/expired
odds_api_key(caught live, not at input-validation time): "The Odds API rejected this run: ... API key is not valid... (error_code=INVALID_KEY)" — exit 1. - A requested sport_key doesn't support the requested markets (e.g. an outrights-only league like
golf_the_open_championship_winnerwithmarkets: ["h2h"]): logged and that sport_key is skipped (INVALID_MARKET_COMBO, itself free); other requested sport_keys still run. - 0 rows with otherwise valid input: not an error — the run succeeds, but the log has an explicit
WARNING: 0 results produced despite valid input (...)line and the run's status message says so.
How it works
- Validate structural input (key present,
devig_method/marketsknown, ...) — fails fast with NO upstream call at all if anything is wrong. GET /v4/sports(free) validates your API key live AND validates everysport/leaguesvalue against the live catalog, before any paid call.GET /v4/sports/{sport}/oddsonce per requested sport_key (a fair per-key share ofmax_events, so one league can't consume the whole budget).- Flatten every
(event, bookmaker, market)triple into one row, de-vig its outcomes directly. - Charge-and-deliver each row atomically — whatever is charged is delivered, and vice versa.
- 0 rows with valid input logs an explicit warning instead of looking like a normal empty run.
Known limitations (documented, not hidden)
- Only "main" lines are fetched, not alternate spreads/totals (The Odds API's
alternate_spreads/alternate_totalsmarkets are out of scope for this actor's low-effort MVP). - Shin de-vig falls back to multiplicative (logged) for a book with ~zero/negative overround — a mathematical edge case (Shin's model has no solution there), not a "doesn't work for 3-way markets" limitation; this actor's test suite validates Shin for both 2-way and 3-way real-odds cases.
max_eventscaps events, not rows. A run can still return far moreevent-rows (and cost far more) thanmax_eventssuggests, since each event fans out into one row per bookmaker × market.- Your Odds API quota is shared across every tool using that key — this actor doesn't track your remaining quota across runs, only logs it per-call (
x-requests-remainingin the run log). sportandleaguesare both The Odds APIsport_keyvalues (there is no separate "sport vs. league" hierarchy in the underlying API) —leaguesis simply "additional sport_keys to fetch in the same run," not a sub-filter withinsport.
Data source
The Odds API (api.the-odds-api.com/v4) — bring your own key, zero scraping, zero anti-bot risk. This actor never ships with a shared/pooled key.