Odds Arbitrage & Value Signal API
Pricing
from $0.01 / 1,000 dataset items
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
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
4 days ago
Last modified
Categories
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:
- 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 astake_splittelling 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. - Value-bet signal —
sharp_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 abovemin_edge_pctversus that fair probability is flagged invalue_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
| 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 "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"]. 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). |
regions | array 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. |
markets | array of strings | ["h2h"] | "h2h" (moneyline/1x2), "spreads" (point spread), "totals" (over/under). Each requested market multiplies quota cost. |
min_edge_pct | number (0–100) | 2.0 | Minimum 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_method | string | "multiplicative" | "multiplicative", "power", or "shin" — how sharp_book's own margin is stripped to compute the fair-probability anchor. |
sharp_book | string | "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_events | integer (1–200) | 25 | Max 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.
| Field | Type | Notes |
|---|---|---|
event | object | id, sport_key, sport_title, commence_time (ISO 8601 UTC), home_team, away_team |
market | string | "h2h", "spreads", or "totals" — always one of the markets you actually requested (see "Known limitations" re: exchange h2h_lay) |
point | number or null | The spread/total line magnitude (e.g. 6.5, 155.5). null for h2h |
devig_method / sharp_book | string | Which method/book this run used (uniform across every row) |
best_odds | object | Keyed 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 |
overround | number or null | Sum 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_pct | number 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_split | object or null | Only 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_flags | array | {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_compared | array of strings | Every bookmaker key that offered this line — the full comparison set, for transparency |
note | string or null | Non-null whenever something was skipped/partial for this row — explains exactly why |
fetched_at | string | When this run fetched this row |
How arbitrage and value are computed, and why a row is never dropped
overround/arb_pct/stake_splitrequire 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 arenullandnoteexplains it, butbest_oddsstill reports whatever outcomes WERE priced.value_flagsis gated independently and more narrowly: it only needs, per (book, outcome) pair, that BOTH that book's own price for that outcome ANDsharp_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_bookis 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/totalsmarket 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)
| Event | Price | When it's charged |
|---|---|---|
event-row | $0.003 | Once per (event, market, line) row returned |
opportunity | $0.03 | Premium — 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 validatesport/leagues) is free — confirmed live.- Each odds fetch costs
(number of markets requested) × (number of regions requested)of your quota, regardless ofbooksfiltering. Requesting all three markets costs 3× a single-market request. - Each additional
leaguesentry is a separate fetch with its own cost. - Auto-adding
sharp_bookto a non-emptybooksfilter does not add to this cost (confirmed live for actor #8:bookmakersfiltering 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: speakingActorInputErrorstating the exact allowed values/range — exit 1, no upstream call made at all. - 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. - 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
- Validate structural input — fails fast with no upstream call if anything is wrong.
GET /v4/sports(free) validates your API key live AND everysport/leaguesvalue against the live catalog.GET /v4/sports/{sport}/oddsonce per requested sport_key (fair per-key share ofmax_events), withsharp_bookauto-added to a non-emptybooksfilter.- Group every (bookmaker, market) entry per event by
(market_key, line)— filtering out any market key you didn't request (see "Known limitations"). - 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 clearingmin_edge_pct. - Charge-and-deliver:
event-rowfor every row, plusopportunityfor rows that actually found something — 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 — 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 anh2h_laymarket object alongsideh2heven when onlymarkets: ["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_bookcoverage varies by region — see "Getting real value signals" above. An absent sharp book is reported vianote, never guessed or substituted.max_eventscaps events, not rows. A run can return far moreevent-rows (and cost far more) thanmax_eventssuggests, 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_splitdescribe 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.