Skip to Content

Middles

Find middle opportunities where two sportsbooks have different lines, creating a gap where both bets can win.

GET /api/v1/opportunities/middles

AuthenticationPermalink for this section

Requires API key. Pro tier or higher required.

Query ParametersPermalink for this section

ParameterTypeDefaultDescription
sportstringallFilter by sport(s), comma-separated (e.g. basketball, football)
leaguestringallFilter by league(s), comma-separated (e.g. nba, nfl, nhl)
sportsbookstringtier-allowedFilter by sportsbook(s), comma-separated. Tier limits enforced.
marketstringallFilter by market type(s), comma-separated (e.g. point_spread, total_points). Supports category aliases — see Odds: Market Category Aliases.
min_profitnumberMinimum ROI % — filters by each middle’s roi_percentage
livebooleantrue = live only, false = prematch only, omit = both
max_odds_agenumberMax odds age in seconds. Filters out stale opportunities.
sortstringqualitySort field: quality (default), ev, probability, middle_size
limitinteger50Results per page (max 200)
offsetinteger0Pagination offset (max 5000)

Example RequestsPermalink for this section

curl -X GET "https://api.sharpapi.io/api/v1/opportunities/middles?league=nfl&min_profit=1" \ -H "X-API-Key: YOUR_API_KEY"

ResponsePermalink for this section

Success (200)Permalink for this section

Real response for GET /api/v1/opportunities/middles?league=mlb&live=false&sort=middle_size&limit=1 (captured 2026-07-02; alt_side2 truncated for brevity):

{ "data": [ { "alt_side1": [ { "book": "kalshi", "external_event_id": "KXMLBTOTAL-26JUL022210SDLAD", "market_id": "KXMLBTOTAL-26JUL022210SDLAD-4", "odds": -1329, "selection_id": "KXMLBTOTAL-26JUL022210SDLAD-4_yes" }, { "book": "novig", "external_event_id": "019f1eb1-8730-7851-aab2-7d433205d1fc", "market_id": "019f20bb-5b05-7841-9c2c-e4fabf16bdcf", "odds": -3471, "selection_id": "019f20bb-5b05-7841-9c2c-e500de0ba6e2" } ], "alt_side2": [ { "book": "kalshi", "external_event_id": "KXMLBTOTAL-26JUL022210SDLAD", "market_id": "KXMLBTOTAL-26JUL022210SDLAD-8", "odds": 182, "selection_id": "KXMLBTOTAL-26JUL022210SDLAD-8_no" }, { "book": "novig", "external_event_id": "019f1eb1-8730-7851-aab2-7d433205d1fc", "market_id": "019f1eb1-8767-7393-9a85-b234a45f6e58", "odds": 170, "selection_id": "019f1eb1-8767-7393-9a85-b25a26e03f2c" } ], "away_team": "San Diego Padres", "best_case_profit": 89.75, "break_even_percent": 5.4, "detected_at": "2026-07-02T18:24:11Z", "event_id": "mlb_dodgers_padres_2026-07-02_b3", "event_name": "San Diego Padres @ Los Angeles Dodgers", "expected_value": 28.32, "guaranteed_roi": null, "home_team": "Los Angeles Dodgers", "id": "102f0c34a65c64b6", "is_guaranteed_profit": false, "is_live": false, "is_player_prop": false, "is_team_total": false, "key_number_probability": 0, "key_numbers": [], "league": "mlb", "league_label": "MLB", "market_label": "Total Runs", "market_overround": 1.054, "market_type": "total_runs", "middle_numbers": [ 4, 5, 6, 7 ], "middle_probability": 0.3525, "middle_size": 4, "odds_age_seconds": 546, "player_name": null, "quality_score": 1.84, "roi_percentage": 28.32, "side1": { "book": "polymarket", "external_event_id": "mlb_dodgers_padres_2026-07-02_b3", "fair_probability": 0.655, "line": 3.5, "market_id": "pm_2763202", "odds_age_seconds": 244.6, "odds_american": -223, "odds_decimal": 1.448, "odds_probability": 0.6904024767801857, "selection": "Over", "selection_id": "2763202_over", "stake_percent": 65.5 }, "side2": { "book": "sx_bet", "external_event_id": "L18646245", "fair_probability": 0.345, "line": 7.5, "market_id": "sx_0x5e051a0f483fc08b7b3d9d5893ca1970a90d693b8072be89ae272e5684be2c6d", "odds_age_seconds": 546, "odds_american": 175, "odds_decimal": 2.75, "odds_probability": 0.36363636363636365, "selection": "Under", "selection_id": "0x5e051a0f483fc08b7b3d9d5893ca1970a90d693b8072be89ae272e5684be2c6d_2", "stake_percent": 34.5 }, "sport": "baseball", "start_time": "2026-07-03T02:11Z", "stat_category": null, "team_name": null, "warnings": [ "HIGH_PROBABILITY", "LARGE_GAP", "STALE_ODDS" ], "worst_case_loss": -5.13, "worst_case_pnl": -5.13 } ], "pagination": { "count": 1, "has_more": true, "limit": 1, "next_offset": 1, "offset": 0, "total": 31 }, "updated_at": "2026-07-02T18:24:11.737562235Z" }

Response HeadersPermalink for this section

X-RateLimit-Limit: 300 X-RateLimit-Remaining: 296 X-RateLimit-Reset: 1707401000 X-Data-Delay: 0 X-Request-Id: 1782526326424224-82519

Error ResponsesPermalink for this section

401 Unauthorized

No key supplied returns missing_api_key; a bad key returns invalid_api_key.

{ "error": { "code": "missing_api_key", "message": "API key required. Pass via X-API-Key header, api_key query parameter, or Bearer token.", "docs": "https://sharpapi.io/docs/authentication" } }

403 Tier Restricted

{ "error": { "code": "tier_restricted", "message": "This endpoint requires Pro tier or higher", "docs": "https://sharpapi.io/pricing", "tier": "hobby", "required_tier": "pro" } }

Response FieldsPermalink for this section

FieldTypeDescription
idstringUnique middle identifier (hash)
event_idstringEvent identifier
event_namestringHuman-readable event name
sportstringSport identifier
leaguestringLeague identifier
market_typestringMarket type (point_spread, total_points, player prop types)
market_labelstringHuman-readable market name
league_labelstringHuman-readable league name
home_teamstringHome team name
away_teamstringAway team name
start_timestring|nullGame start time (ISO 8601)
side1objectFirst side of the middle bet
side2objectSecond side of the middle bet
middle_sizenumberSize of the middle gap in points
middle_numbersnumber[]Integer values that would hit the middle
middle_probabilitynumberEstimated probability of the middle hitting. Sport- and market-specific: a per-sport multiplicative calibration factor (backtested against settled outcomes and refreshed as data accrues, most recently 2026-07-02) is applied to the raw per-number sum, then clamped to a ceiling of 0.50.
expected_valuenumberEV in dollars per $100 total wagered
roi_percentagenumberEV as percentage of total stake
worst_case_lossnumberworst_case_pnl clamped to <= 0 — the downside when the middle misses (0 on guaranteed-profit middles, negative otherwise)
worst_case_pnlnumberRaw worst-case profit-and-loss per $100 total stake. Positive on guaranteed-profit middles (arb + middle — the worst case still profits), negative otherwise. Equals worst_case_loss only when negative.
best_case_profitnumberProfit when middle hits (both bets win)
break_even_percentnumberMinimum hit rate needed to break even
odds_age_secondsnumber|nullAge of stalest underlying odds at detection time
is_guaranteed_profitbooleanTrue when worst_case_loss >= 0 (arb + middle)
guaranteed_roinumber|nullWorst-case ROI% when guaranteed (null otherwise)
key_numbersnumber[]Key numbers in middle range (e.g., NFL 3, 7)
key_number_probabilitynumberCombined probability of key numbers only
quality_scorenumberMulti-factor quality metric (0-100)
market_overroundnumberCombined implied probability across both sides
is_livebooleanWhether the game is currently in-play
is_player_propbooleanWhether this is a player prop middle
player_namestring|nullPlayer name (if player prop)
stat_categorystring|nullStat type (if player prop, e.g., points, rebounds)
is_team_totalbooleanWhether this is a team-total middle
team_namestring|nullTeam name (if team total)
alt_side1object[]Other books offering side 1’s selection at the same line — {book, odds, selection_id, market_id, external_event_id} per entry, sorted best price first. Empty array when no alternates.
alt_side2object[]Other books offering side 2’s selection at the same line (same shape as alt_side1)
warningsstring[]Warning flags (see below)
detected_atstringWhen the middle was detected (ISO 8601)

Side ObjectPermalink for this section

FieldTypeDescription
bookstringSportsbook for this side
selectionstringThe selection name
linenumberThe line value at this sportsbook
odds_americannumberAmerican odds
odds_decimalnumberDecimal odds
odds_probabilitynumberImplied probability (0-1)
fair_probabilitynumberFair probability adjusted for overround
stake_percentnumberRecommended stake allocation (% of total)
odds_age_secondsnumber|nullAge of this side’s odds at detection time
selection_idstring|nullSportsbook’s native selection/outcome ID
market_idstring|nullSportsbook’s native market ID
external_event_idstring|nullSportsbook’s native event ID
homeobject|undefinedReserved — not currently emitted on this endpoint. Nested home-team reference: {id, numerical_id, name, abbreviation, logo, city, mascot, conference, division}. See Entity reference IDs.
awayobject|undefinedReserved — not currently emitted on this endpoint. Nested away-team reference (same shape as home).
sport_refobject|undefinedReserved — not currently emitted on this endpoint. Nested sport reference: {id, numerical_id, name}.
league_refobject|undefinedReserved — not currently emitted on this endpoint. Nested league reference: {id, numerical_id, label}.
market_refobject|undefinedReserved — not currently emitted on this endpoint. Nested market reference: {id, numerical_id, label}.
sportsbook_refobject|undefinedReserved — not currently emitted on this endpoint. Nested sportsbook reference for this side’s book: {id, numerical_id, label}.

Nested reference blocks on each side (reserved)Permalink for this section

The side schema reserves six nested reference objects (home, away, sport_ref, league_ref, market_ref, sportsbook_ref) mirroring the blocks on /odds. As of 2026-07-02 the middles engine does not stamp them, so live side1 / side2 objects never include these blocks — today they appear only on /odds rows for atlas-mapped books. Treat every block (and every inner field) as optional and never require them; when emitted they will be purely additive — the flat top-level fields (sport, league, home_team, away_team, market_type) and per-side book / selection strings are unchanged. See Entity reference IDs for the shape and guarantees.

Warning FlagsPermalink for this section

WarningMeaning
HIGH_PROBABILITYCalibrated middle probability exceeds 35% — at or beyond the highest empirically realized hit rate; treat as notable (and double-check the lines)
LARGE_GAPMiddle size exceeds 3 points — verify lines are correct
STALE_ODDSOne or both odds are older than 300 seconds (5 minutes)
LIVE_GAMEGame is currently in progress
GUARANTEED_PROFITWorst-case loss is positive — this is also an arbitrage

Detail EndpointPermalink for this section

Fetch a single middle opportunity by its ID.

GET /api/v1/opportunities/middles/:id
ParameterTypeDescription
idpathRequired. 16-character hex hash (e.g., a1b2c3d4e5f67890)

Returns { "data": <middle>, "updated_at": "<ISO>" } — the middle uses the same schema as the list items. Returns 404 if the ID is unknown or malformed.

curl -X GET "https://api.sharpapi.io/api/v1/opportunities/middles/a1b2c3d4e5f67890" \ -H "X-API-Key: YOUR_API_KEY"

What Are Middles?Permalink for this section

A middle occurs when two sportsbooks offer different lines on the same market, creating a numerical gap where both bets can win simultaneously.

Unlike arbitrage (which guarantees profit), middles give you a chance to win both sides of a bet if the final result lands in the “middle” of the two lines.

Spread Middle ExamplePermalink for this section

DraftKings: Chiefs -2.5 @ -110 FanDuel: Bills +7.5 @ -108

You bet:

  • Chiefs -2.5 at DraftKings
  • Bills +7.5 at FanDuel

Possible outcomes:

Chiefs Win ByChiefs -2.5Bills +7.5Result
1-2 pointsLoseWinSplit (small loss)
3, 4, 5, 6, or 7WinWinMIDDLE HIT
8+ pointsWinLoseSplit (small loss)

If the Chiefs win by 3-7, both bets win. The numbers 3 and 7 are NFL key numbers (field goal and touchdown), making this middle especially valuable.

Key NumbersPermalink for this section

Certain sports have scoring increments that spike the probability distribution:

SportKey NumbersWhy
NFL3, 7, 10, 14Field goal (3), touchdown (7), combinations
NHL1Most games decided by 1 goal (~47%)
MLB1Most games decided by 1 run (~30%)
Soccer1Most matches decided by 1 goal (~38%)

When a middle contains key numbers, the key_numbers and key_number_probability fields highlight this. An NFL middle spanning 3 and 7 has a combined key number probability of ~24.2%.

Guaranteed Profit (Arb + Middle)Permalink for this section

When is_guaranteed_profit is true, the middle is also an arbitrage — the worst case still yields a profit. This is the rarest and most valuable type of middle. The guaranteed_roi field shows the minimum return even if the middle misses.

Middle Size GuidePermalink for this section

Middle SizeQualityTypical Markets
0.5 pointsNarrowRare opportunities, low hit rate
1.0 pointsStandardCommon in spreads and totals
1.5 - 2.0 pointsGoodLook for these actively
2.5+ pointsExcellentUncommon but highly profitable

Sort by middle_size to surface the widest gaps first, and use min_profit for a minimum-ROI floor. Larger middles have a higher probability of landing in the gap, making them more consistently profitable over time.

Risk ConsiderationsPermalink for this section

Middles are not risk-free like arbitrage (unless is_guaranteed_profit is true). When the result falls outside the middle, you lose the vig on the losing side (typically 4-5% of one leg). However, when a middle hits, you win both bets, which more than compensates over time.

Odds FreshnessPermalink for this section

The odds_age_seconds field shows how old the underlying odds were when the middle was detected. Each side also has its own odds_age_seconds. If the stalest side exceeds 300 seconds (5 minutes), the STALE_ODDS warning is added. Stale odds may have already moved — act quickly on fresh opportunities.

Best PracticesPermalink for this section

  1. Focus on larger middles — Sort by middle_size and prefer wider gaps for better hit rates
  2. Watch for key numbers — Middles containing NFL 3/7 or NHL/MLB/soccer 1 have elevated hit rates
  3. Prioritize guaranteed profit — Filter for is_guaranteed_profit to find risk-free opportunities
  4. Check odds freshness — Low odds_age_seconds means the lines are current and actionable
  5. Combine with the summary endpoint — Use /middles/summary for dashboard overviews
  6. Act quickly — Lines converge as books adjust, closing the middle window
  7. Use streaming — Subscribe to middles:detected events via SSE or WebSocket for real-time alerts
Last updated on