Odds Arbitrage & Value Signal API avatar

Odds Arbitrage & Value Signal API

Pricing

from $0.01 / 1,000 dataset items

Go to Apify Store
Odds Arbitrage & Value Signal API

Odds Arbitrage & Value Signal API

Cross-book best-price comparison via The Odds API (bring your own key): detect guaranteed-profit arbitrage (negative overround) with a stake-split calculator, and flag value bets against a de-vigged sharp-book (Pinnacle) fair price.

Pricing

from $0.01 / 1,000 dataset items

Rating

0.0

(0)

Developer

Daniel Posztos

Daniel Posztos

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

4 days ago

Last modified

Share

Odds Arbitrage & Value Signal API — Cross-Book De-Vig Value Layer

SEO title: Odds Arbitrage & Value Signal API — Sports Betting Edge SEO description: Detect guaranteed-profit arbitrage across sportsbooks and flag value bets against a de-vigged Pinnacle fair price, via The Odds API (bring your own key). No scraping, no anti-bot.

Raw sportsbook odds are a commodity — dozens of feeds publish them. The signal is the product: this actor pulls odds from The Odds API (you bring your own key — zero scraping, zero anti-bot risk), the same BYO-key source and de-vig math as actor #8 (Sports Odds → Fair Probability API), and adds a value layer on top:

  1. Arbitrage detection — for every (event, market, betting line), find the single best (highest) decimal odds for each outcome across every book you're comparing. If those best-odds imply a combined probability under 100%, a guaranteed-profit arbitrage exists — reported as arb_pct, with a stake_split telling you exactly what fraction of your stake to place on each outcome (at its best-odds book) for an equal payout no matter who wins.
  2. Value-bet signalsharp_book (default "pinnacle", the traditional sharp reference in the betting industry) is de-vigged (multiplicative/power/Shin — your choice) to get a fair probability per outcome. Every (book, outcome) pair whose own price implies an edge at or above min_edge_pct versus that fair probability is flagged in value_flags.

Built for betting modelers, arbitrage hunters, cross-platform arbitrage tooling (pairs naturally with actor #7, Prediction Market Odds API), and AI agents that need a structured "is there money on the table here" signal instead of raw odds tables.

⚠️ Odds move fast. Every row here is a snapshot from the moment this run fetched it. This is a research/alert tool for spotting a signal worth checking, not an execution guarantee — by the time you place a bet, the price (or the arbitrage window) may already be gone. Always re-check the live price at the actual sportsbook before staking money.

Why this exists

A raw odds feed just gives you numbers. Turning those numbers into a decision requires two separate calculations most tools don't do for you:

  • Is there a mathematical arbitrage right now? Comparing the best price for every outcome across several books, then checking whether their combined implied probability dips under 100% — a genuine, bookmaker-agnostic guaranteed return if all legs are placed.
  • Is any individual book's price generous relative to the market's real (de-vigged) view? This requires a trusted fair-price anchor (a "sharp" book whose own vig has been removed) to compare every other book's price against.

This actor computes both, on every event/market/line it fetches, and never silently drops a row for thin data — a market that can't support one or the other calculation still ships with a documented note explaining exactly why.

Input

FieldTypeDefaultDescription
odds_api_keystring (secret)Required. Your own The Odds API key (free tier: 500 requests/month). Bring-your-own-key: never logged, never in output.
sportstringRequired. A The Odds API sport_key, e.g. "basketball_nba", "soccer_epl", or "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.
leaguesarray of strings[]Additional sport_keys to fetch in the SAME run alongside sport (each costs its own separate quota).
booksarray of strings[]Bookmaker keys to restrict to, e.g. ["draftkings", "fanduel", "pinnacle"]. If non-empty and sharp_book isn't in the list, it's added automatically (free — doesn't change quota cost). Leave empty to compare every bookmaker available in regions (recommended for arbitrage — more books = more chance of a real cross-book gap).
regionsarray of strings["us"]Odds API region codes (us, uk, eu, au, ...). Pinnacle is not always covered by region "us" — see "Getting real value signals" below. Each additional region multiplies quota cost.
marketsarray of strings["h2h"]"h2h" (moneyline/1x2), "spreads" (point spread), "totals" (over/under). Each requested market multiplies quota cost.
min_edge_pctnumber (0–100)2.0Minimum edge (percentage points) a book's own price must clear versus sharp_book's de-vigged fair probability to be reported in value_flags.
devig_methodstring"multiplicative""multiplicative", "power", or "shin" — how sharp_book's own margin is stripped to compute the fair-probability anchor.
sharp_bookstring"pinnacle"The Odds API bookmaker key used as the value-bet anchor. If it doesn't cover a given event/market/line, value_flags is left empty for that row with an explicit note — never silently wrong.
max_eventsinteger (1–200)25Max DISTINCT events, fair-shared across every requested sport_key. Each event still produces one row per (market × distinct line) it has.

Getting real value signals

value_flags needs sharp_book (Pinnacle by default) to actually be one of the books returned for a given game. Confirmed live (2026-07-07): Pinnacle is not consistently covered by The Odds API's "us" region (the input default) for every sport — it shows up reliably for e.g. MLB/NPB/KBO baseball under "us", but was absent for a WNBA slate checked the same day. If your value_flags keep coming back empty with the "Sharp book 'pinnacle' is not among the books..." note, add "eu" to regions (Pinnacle is a European-licensed book and shows up there far more consistently), or list "pinnacle" explicitly in books.

Worked example — input

{
"odds_api_key": "YOUR_THE_ODDS_API_KEY",
"sport": "upcoming",
"markets": ["h2h"],
"regions": ["us", "eu"],
"devig_method": "multiplicative",
"sharp_book": "pinnacle",
"min_edge_pct": 1.0,
"max_events": 8
}

Worked example — a real arbitrage (live data, 2026-07-07)

{
"event": {
"id": "f0620d5ff3f64875ff71939d62f51d91",
"sport_key": "tennis_atp_wimbledon",
"sport_title": "ATP Wimbledon",
"commence_time": "2026-07-07T06:45:00Z",
"home_team": "Jiri Lehecka",
"away_team": "Alexander Zverev"
},
"market": "h2h",
"point": null,
"devig_method": "multiplicative",
"sharp_book": "pinnacle",
"best_odds": {
"Alexander Zverev": { "book": "betfair_ex_eu", "odds": 1.07, "point": null },
"Jiri Lehecka": { "book": "onexbet", "odds": 16.0, "point": null }
},
"overround": 0.9970794392523364,
"arb_pct": 0.29291154071471315,
"stake_split": {
"Alexander Zverev": 0.9373169302870533,
"Jiri Lehecka": 0.0626830697129467
},
"value_flags": [],
"books_compared": ["betclic_fr", "betfair_ex_eu", "betmgm", "bovada", "coolbet", "draftkings", "fanduel", "marathonbet", "matchbook", "onexbet", "sport888", "unibet_nl", "unibet_se", "winamax_de", "winamax_fr"],
"note": "Sharp book 'pinnacle' is not among the books offering this event/market/line (either it doesn't cover this game, or it wasn't included in 'books'/'regions'); value_flags left empty for this row -- see README \"Getting real value signals\".",
"fetched_at": "2026-07-07T08:06:08.024198+00:00"
}

Betfair Exchange's back price on Zverev (1.07) and onexbet's price on Lehecka (16.0) together imply just 99.71%, not 100% — a real, if thin (0.29%), cross-book arbitrage: staking 93.73% of your bankroll on Zverev at 1.07 and 6.27% on Lehecka at 16.0 returns the exact same amount (100.29% of stake) whichever way the match goes.

Worked example — a real value flag (live data, 2026-07-07)

{
"event": { "sport_key": "baseball_kbo", "sport_title": "KBO", "home_team": "Hanwha Eagles", "away_team": "NC Dinos" },
"market": "h2h",
"best_odds": {
"Hanwha Eagles": { "book": "lowvig", "odds": 2.15, "point": null },
"NC Dinos": { "book": "onexbet", "odds": 1.85, "point": null }
},
"overround": 1.005656819610308,
"arb_pct": null,
"stake_split": null,
"value_flags": [
{ "book": "onexbet", "outcome": "NC Dinos", "edge_pct": 0.909090909090926, "fair_prob": 0.5454545454545455 }
],
"books_compared": ["betclic_fr", "betmgm", "betonlineag", "betrivers", "betsson", "bovada", "coolbet", "draftkings", "everygame", "fanduel", "leovegas_se", "lowvig", "mybookieag", "nordicbet", "onexbet", "pinnacle", "tipico_de", "unibet_fr", "unibet_se", "williamhill"],
"note": null
}

Pinnacle's own price de-vigs to a 54.55% fair probability for NC Dinos; onexbet quotes 1.85 on that same outcome, which is a 0.91% edge over fair — flagged since min_edge_pct was 0.1 for this run. No arbitrage here (overround is 1.0057, just above 1.0), but there IS a value edge on one specific book.

Output schema

One row per (event, market, line) — a "line" is the market's own distinct point value(s) (e.g. -6.5/+6.5 for one spread, 155.5 for one total; h2h has no line at all). See .actor/dataset_schema.json for the full JSON Schema.

FieldTypeNotes
eventobjectid, sport_key, sport_title, commence_time (ISO 8601 UTC), home_team, away_team
marketstring"h2h", "spreads", or "totals" — always one of the markets you actually requested (see "Known limitations" re: exchange h2h_lay)
pointnumber or nullThe spread/total line magnitude (e.g. 6.5, 155.5). null for h2h
devig_method / sharp_bookstringWhich method/book this run used (uniform across every row)
best_oddsobjectKeyed by outcome name → {book, odds, point}: the single best (highest) decimal odds for that outcome across every book compared, and which book offered it. Populated for every outcome that had at least one valid quote, even if others didn't
overroundnumber or nullSum of best-odds implied probabilities. > 1.0 = normal (no cross-book arb); < 1.0 = an arbitrage exists. null only when not every outcome had any priced quote
arb_pctnumber or null(1/overround - 1) x 100. Only non-null when a real arbitrage exists (overround < 1.0) — this field means "an arbitrage was detected", never a negative number for a normal vigged book
stake_splitobject or nullOnly present alongside arb_pct. Fraction of total stake per outcome (at its best_odds book) for an equal guaranteed return. Sums to 1.0
value_flagsarray{book, outcome, edge_pct, fair_prob} for every (book, outcome) pair clearing min_edge_pct versus sharp_book's de-vigged fair probability. Empty (not missing) when sharp_book didn't cover this line, or nothing cleared the bar
books_comparedarray of stringsEvery bookmaker key that offered this line — the full comparison set, for transparency
notestring or nullNon-null whenever something was skipped/partial for this row — explains exactly why
fetched_atstringWhen this run fetched this row

How arbitrage and value are computed, and why a row is never dropped

  • overround/arb_pct/stake_split require every outcome in the (market, line) group to have at least one validly-priced book — a guaranteed-return arbitrage needs a price for every possible outcome, not just some. If any outcome is completely unpriced across every compared book, these three fields are null and note explains it, but best_odds still reports whatever outcomes WERE priced.
  • value_flags is gated independently and more narrowly: it only needs, per (book, outcome) pair, that BOTH that book's own price for that outcome AND sharp_book's de-vigged fair probability for that outcome exist. A book with one suspended outcome still gets flagged on its other, still-priced outcome(s) — value analysis doesn't wait for full best-odds coverage.
  • sharp_book is never special-cased out of its own flagging pass. De-vigging a book's own odds and comparing its own price back against its own resulting fair probability always produces a uniformly negative apparent "edge" for every one of its own outcomes (removing a book's margin against itself can't create positive edge for itself) — proven in this actor's test suite across all three de-vig methods, not just assumed.
  • Different lines are never merged. Confirmed live for actor #8 that a bookmaker's own spreads/totals market object is already exactly ONE line (e.g. -6.5/+6.5, never mixed with -7/+7); this actor groups every bookmaker's market objects by (market_key, point_signature) so that comparing "best odds" across books is always apples-to-apples, never accidentally pairing two different point spreads as if they were the same bet.

Pricing (pay-per-event)

EventPriceWhen it's charged
event-row$0.003Once per (event, market, line) row returned
opportunity$0.03Premium — once per row that ACTUALLY contains an arbitrage and/or ≥1 value flag. Never charged for a "clean" row with neither

Plus Apify's own apify-actor-start synthetic event (first 5 seconds of compute free, platform-managed). Your own Odds API quota is separate — a free-tier key is capped at 500 requests/month, and markets/regions multiply how many of those a single run costs.

Example: 200 event-rows returned, ~15 of which actually contain an opportunity (a realistic order of magnitude — most rows carry neither by construction): 200 x $0.003 = $0.60, plus 15 x $0.03 = $0.45, total $1.05.

If a run's cost would exceed your Max total charge USD, the actor stops delivering further rows at exactly that point — every delivered row was paid for, nothing paid-for is ever dropped, no crash. The opportunity premium is charged before a row is pushed (not after): if its budget alone runs out, that row is still delivered as a normal event-row with its arbitrage/value content stripped out and a note explaining why, rather than shipping premium content for free.

Quota notes (The Odds API, your own key)

  • GET /v4/sports (used internally to validate sport/leagues) is free — confirmed live.
  • Each odds fetch costs (number of markets requested) × (number of regions requested) of your quota, regardless of books filtering. Requesting all three markets costs 3× a single-market request.
  • Each additional leagues entry is a separate fetch with its own cost.
  • Auto-adding sharp_book to a non-empty books filter does not add to this cost (confirmed live for actor #8: bookmakers filtering is free).
  • Free-tier keys get 500 requests/month total (shared across every tool using that key).

Error messages

  • Missing/invalid odds_api_key, sport, markets, devig_method, sharp_book, min_edge_pct, max_events: speaking ActorInputError stating the exact allowed values/range — exit 1, no upstream call made at all.
  • Unknown sport/leagues value: "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.
  • Invalid/expired odds_api_key (caught live): "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 with markets: ["h2h"]): logged and that sport_key is skipped (INVALID_MARKET_COMBO, 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

  1. Validate structural input — fails fast with no upstream call if anything is wrong.
  2. GET /v4/sports (free) validates your API key live AND every sport/leagues value against the live catalog.
  3. GET /v4/sports/{sport}/odds once per requested sport_key (fair per-key share of max_events), with sharp_book auto-added to a non-empty books filter.
  4. Group every (bookmaker, market) entry per event by (market_key, line) — filtering out any market key you didn't request (see "Known limitations").
  5. For each group: find best odds per outcome, compute overround/arbitrage/stake-split (or document why not), then de-vig sharp_book's own odds and flag every (book, outcome) pair clearing min_edge_pct.
  6. Charge-and-deliver: event-row for every row, plus opportunity for rows that actually found something — whatever is charged is delivered, and vice versa.
  7. 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 — same MVP scope as actor #8.
  • Exchange bookmakers can return a companion market you didn't ask for. Confirmed live (2026-07-07): Betfair Exchange (betfair_ex_eu) returns an h2h_lay market object alongside h2h even when only markets: ["h2h"] was requested (their back/lay quotes are two separate market objects upstream). This actor filters output to ONLY the market keys you explicitly requested — h2h_lay (and any other unrequested companion market) is silently dropped from output, never billed.
  • sharp_book coverage varies by region — see "Getting real value signals" above. An absent sharp book is reported via note, never guessed or substituted.
  • max_events caps events, not rows. A run can return far more event-rows (and cost far more) than max_events suggests, since each event fans out into one row per (market × distinct line).
  • A thin/rare arbitrage can vanish before you can act on it — see the disclaimer at the top of this README. arb_pct/stake_split describe the moment this row was fetched, not a standing offer.
  • Shin de-vig falls back to multiplicative (logged) for a book with ~zero/negative overround — a mathematical edge case, not a "doesn't work for 3-way markets" limitation.
  • Your Odds API quota is shared across every tool using that key — this actor logs it per-call but doesn't track cross-run cumulative usage.

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.