Krok Odds

Opportunities API endpoints

Krok's built-in scanners — arbitrage, positive EV, middles, low-holds, player props and same-game multi (SGM) picks, already devigged against a blended sharp baseline.

7 of 7 endpoints · all categories · get a free API key

GET/api/v1/opportunitiesFree tier

Umbrella/legacy endpoint that dispatches to the same underlying data as the dedicated opportunity-type routes, selected via `type`.

Legacy/umbrella wrapper around the opportunities loader — response shape per row depends on `type` and is not normalized to one schema across types. Response includes a static `endpoints` hint block pointing to the racing endpoints (`/api/v1/racing/arbs`, `/api/v1/racing/movers`), which do not correspond to any of the 14 files audited here. Rate-limit headers use `limit` (the row cap) rather than the usual free/api tier request cap seen on other routes. Sports rows are gated on the 12h commence cutoff plus the same 90-min detection-age / sane-odds gate as the dedicated routes; `racing_arbs`/`racing_movers`/`racing` are gated on a 10-min detection age (two cycles of their 5-min writers).

Query parameters
NameTypeRequiredDefaultDescription
typeenum(all|positive-ev|snipes|middles|racing|playerprops)NoallWhich opportunity category to fetch; `positive-ev` is aliased internally to `snipes`. Determines the feature-gate applied (snipes→positive_ev, middles→middles, racing→racing, playerprops→player_props; `all` has no per-type gate).
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport name; uppercased before matching.
minvaluenumberNo0Minimum opportunity value/edge threshold (also accepts `min_value`).
min_valuenumberNo0Alias for minvalue.
limitnumberNo100Max rows returned. Clamped to 100 (free) / 500 (api).
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities?type=all"
Example response
{
  "success": true,
  "data": [
    {
      "id": "snipe_abc123",
      "sport": "NBA",
      "sport_key": "basketball_nba",
      "event": "Boston Celtics vs Los Angeles Lakers",
      "market": "h2h",
      "selection1": "Boston Celtics",
      "bookmaker1": "sportsbet",
      "odds1": 2.1,
      "value": 6.4,
      "commence_time": "2026-09-03T23:10:00Z"
    }
  ],
  "meta": {
    "tier": "free",
    "limit": 100,
    "requested_limit": 100,
    "timestamp": "2026-09-03T10:06:00Z"
  },
  "endpoints": [
    {
      "path": "/api/v1/racing/arbs",
      "description": "Australian racing arbitrage opportunities",
      "parameters": [
        "venue",
        "race_type (T/H/G)",
        "minedge",
        "limit"
      ]
    },
    {
      "path": "/api/v1/racing/movers",
      "description": "Racing steamers and drifters (significant market movers)",
      "parameters": [
        "venue",
        "race_type (T/H/G)",
        "movement_type (steamer|drifter)",
        "min_movement",
        "limit"
      ]
    }
  ]
}
GET/api/v1/opportunities/arbitrageFree tier

Cross-bookmaker arbitrage (surebet) opportunities — two legs that lock in guaranteed profit regardless of outcome.

Supports keyset pagination via cursor/next_cursor and field projection via `fields`. Drops arbs for events that started >12h ago and rows whose `price_observed_at` (the price-read time, not the write time) is >90min old, rows carrying no price-read stamp at all, or rows with insane odds (the price-freshness check/the odds sanity check). exchange legs are stripped for hobby-equivalent (free) tier via the exchange filter. `status` (live/upcoming) is derived from commence_time, not stored. Cache 300s both tiers.

Query parameters
NameTypeRequiredDefaultDescription
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport (also accepts sport_key as fallback); uppercased.
minvaluenumberNo0Minimum arb value/edge % (also accepts min_value).
min_valuenumberNo0Alias for minvalue.
bookmakerstringNo–Substring match against either leg's bookmaker (case-insensitive).
limitnumberNo100Max rows per page. Clamped to 100 (free) / 10000 (api).
cursorstringNo–Opaque keyset-pagination cursor from a prior response's meta.next_cursor.
fieldsstring (comma-separated)No–Field projection — restrict each row to only the named fields.
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/arbitrage?sport_key=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "arb_9f2c1a",
      "event": "Boston Celtics vs Los Angeles Lakers",
      "home_team": "Boston Celtics",
      "away_team": "Los Angeles Lakers",
      "sport": "NBA",
      "sport_key": "basketball_nba",
      "market": "h2h",
      "selection1": "Boston Celtics",
      "selection2": "Los Angeles Lakers",
      "bookmaker1": "sportsbet",
      "bookmaker2": "ladbrokes",
      "odds1": 2.05,
      "odds2": 2.15,
      "bet_link1": "https://www.sportsbet.com.au/...",
      "bet_link2": "https://www.ladbrokes.com.au/...",
      "line": null,
      "value": 2.35,
      "instructions": "Stake $511.20 on Boston Celtics @ sportsbet, $488.80 on Los Angeles Lakers @ ladbrokes",
      "tool_type": "surebet",
      "status": "upcoming",
      "commence_time": "2026-09-03T23:10:00Z",
      "updated_at": "2026-09-03T09:55:00Z"
    }
  ],
  "meta": {
    "count": 1,
    "tier": "free",
    "limit": 100,
    "requested_limit": 100,
    "next_cursor": "eyJ2IjoyLjM1LCJpZCI6ImFyYl85ZjJjMWEifQ==",
    "filters": {
      "sport": null,
      "sport_key": null,
      "bookmaker": null,
      "min_value": null
    },
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 100,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}
GET/api/v1/opportunities/low-holdsFree tier

Two-way markets where the bookmaker overround (vig) across two books is unusually low, near break-even for bettors.

Fastest-refreshing opportunities route: cache 60s both tiers vs 300s for arbitrage/middles/positive-ev. Sorted ascending by hold percentage (lowest hold first). Same staleness (12h commence cutoff) and price-freshness (90min) filtering, plus the exchange filter, as arbitrage. Keyset pagination + field projection supported.

Query parameters
NameTypeRequiredDefaultDescription
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport (also accepts sport_key fallback); uppercased.
minvaluenumberNo0Minimum value threshold (also accepts min_value); checks value/holdPct alias chain.
min_valuenumberNo0Alias for minvalue.
max_holdnumberNo–Upper bound on hold percentage (holdPct or value).
bookmakerstringNo–Substring match against either leg's bookmaker.
limitnumberNo100Max rows per page. Clamped to 100 (free) / 10000 (api).
cursorstringNo–Opaque keyset-pagination cursor.
fieldsstring (comma-separated)No–Field projection list.
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/low-holds?sport_key=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "lowhold_7ab391",
      "sport": "AFL",
      "sport_key": "aussierules_afl",
      "event": "Collingwood vs Essendon",
      "home_team": "Collingwood",
      "away_team": "Essendon",
      "market": "h2h",
      "commence_time": "2026-09-03T08:40:00Z",
      "bookmaker1": "betright",
      "selection1": "Collingwood",
      "odds1": 1.87,
      "bookmaker2": "tab",
      "selection2": "Essendon",
      "odds2": 2.05,
      "bet_link1": "https://www.betright.com.au/...",
      "bet_link2": "https://www.tab.com.au/...",
      "line": null,
      "value": 1.8,
      "hold_pct": 1.8,
      "profit_percent": 1.8,
      "instructions": "Combined hold across both books is 1.8% — near break-even.",
      "tool_type": "low_hold",
      "status": "upcoming",
      "updated_at": "2026-09-03T09:50:00Z"
    }
  ],
  "meta": {
    "count": 1,
    "tier": "free",
    "limit": 100,
    "requested_limit": 100,
    "next_cursor": null,
    "filters": {
      "sport": "AFL",
      "sport_key": null,
      "bookmaker": null,
      "min_value": null,
      "max_hold": null
    },
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 100,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}
GET/api/v1/opportunities/middlesFree tier

Middle opportunities — two different lines/points across bookmakers where both bets can win if the result lands between them.

Only middles route without a `filters` block in meta (present on arbitrage/low-holds/positive-ev but not middles). Same 12h staleness + 90min freshness filtering as arbitrage. No the exchange filter call here (unlike arbitrage/low-holds), so exchange legs are NOT stripped for free tier on this route. Cache 300s both tiers.

Query parameters
NameTypeRequiredDefaultDescription
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport (also accepts sport_key fallback); uppercased.
minvaluenumberNo0Minimum middle value (also accepts min_value).
min_valuenumberNo0Alias for minvalue.
limitnumberNo100Max rows per page. Clamped to 100 (free) / 10000 (api).
cursorstringNo–Opaque keyset-pagination cursor.
fieldsstring (comma-separated)No–Field projection list.
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/middles?sport_key=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "middle_44d1c9",
      "event": "Sydney Swans vs Geelong Cats",
      "home_team": "Sydney Swans",
      "away_team": "Geelong Cats",
      "sport": "AFL",
      "sport_key": "aussierules_afl",
      "market": "spreads",
      "selection1": "Sydney Swans -6.5",
      "selection2": "Geelong Cats +9.5",
      "bookmaker1": "sportsbet",
      "bookmaker2": "pointsbet",
      "odds1": 1.91,
      "odds2": 1.95,
      "bet_link1": "https://www.sportsbet.com.au/...",
      "bet_link2": "https://www.pointsbet.com.au/...",
      "value": 4.2,
      "line1": -6.5,
      "line2": 9.5,
      "middle_window": 3,
      "worst_case_loss": -12.5,
      "best_case_profit": 87.5,
      "instructions": "Bet Sydney -6.5 @ sportsbet and Geelong +9.5 @ pointsbet.",
      "tool_type": "middle",
      "status": "upcoming",
      "commence_time": "2026-09-03T08:40:00Z",
      "updated_at": "2026-09-03T09:52:00Z"
    }
  ],
  "meta": {
    "count": 1,
    "tier": "free",
    "limit": 100,
    "requested_limit": 100,
    "next_cursor": null,
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 100,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}
GET/api/v1/opportunities/player-propsFree tier

Individual player-prop bets with a positive expected-value edge, optionally enriched with historical hit-rate stats.

`historical_stats` is only populated when include_stats=true AND tier !== 'free' — free-tier requests get `historical_stats: undefined` (omitted key) even if include_stats=true. Underlying query scans a 12h commence_time window (max 5000 rows) then sorts by the edge percentage in memory rather than at the DB level, because there's no index for a global the edge percentage sort (avoids Postgres statement timeout). No cursor/fields projection support (unlike arbitrage/low-holds/middles/positive-ev). Filters out rows where player_name looks like a market/selection label (regex-based artifact cleanup). Cache 60s both tiers. **No `bet_link` is served.** Every row includes `price_age_seconds` (integer seconds since its first usable price stamp, using explicit capture/update/detection fields before the fallback `timestamp`; future stamps report 0) and `stale` (boolean, true at 90 minutes or older). Quotes are retained through exactly 24 hours and excluded only beyond 24 hours; the event kickoff cutoff still applies. Missing or unparseable stamps fail open with `price_age_seconds: null` and `stale: false` (unknown age). Age and stale status are recalculated at request time. **`commence_time` is served** as ISO 8601 from the row's stored event kickoff (the `commence_time` column, falling back to the JSONB `commencetime`); it is `null` only when the row carries none of those keys — the per-capture scraper `timestamp` is not treated as the kickoff. With the value present the 12h stale window now applies as intended (a `null` kickoff previously made that comparison a no-op).

Query parameters
NameTypeRequiredDefaultDescription
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport (also accepts sport_key fallback); lowercased (unlike other opportunities routes which uppercase).
marketstringNo–Exact match against market_key, e.g. `player_points`.
playerstringNo–Substring match against player_name (case-insensitive).
bookmakerstringNo–Substring match against bookmaker.
min_evnumberNo0Minimum the edge percentage threshold.
include_statsbooleanNofalseWhen `true` (and tier is not free), joins in historical_stats (hit rates, streaks) per prop from the prop-stats store.
limitnumberNo100Max rows returned. Clamped to 100 (free) / 10000 (api).
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/player-props?sport_key=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "prop_e8a112",
      "event_id": "basketball_nba_20260903_lal_bos",
      "event": "Boston Celtics vs Los Angeles Lakers",
      "sport": "basketball_nba",
      "sport_key": "basketball_nba",
      "market_key": "player_points",
      "player_name": "Jayson Tatum",
      "line": 27.5,
      "side": "over",
      "odds": 1.95,
      "bookmaker": "sportsbet",
      "ev_percentage": 7.8,
      "commence_time": "2026-09-03T23:10:00Z",
      "price_age_seconds": 18000,
      "stale": true,
      "historical_stats": {
        "sample_size": 22,
        "hit_rate_over": 0.68,
        "hit_rate_under": 0.32,
        "last_5_results": [
          true,
          true,
          false,
          true,
          true
        ],
        "last_5_hit_rate": 0.8,
        "streak": "over_2",
        "line_bucket": "25-30",
        "updated_at": "2026-09-02T22:00:00Z"
      }
    }
  ],
  "meta": {
    "count": 1,
    "tier": "api",
    "limit": 100,
    "requested_limit": 100,
    "filters": {
      "sport": null,
      "sport_key": "basketball_nba",
      "player": null,
      "bookmaker": null,
      "market": null,
      "min_ev": null
    },
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 500,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}
GET/api/v1/opportunities/positive-evFree tier

Single-leg bets priced favorably against a sharp reference price (e.g. exchange fair odds) — positive expected value snipes.

`bookmaker2` falls back to the synthetic string 'Betfair Fair' (a reference price, not a bettable book) when no second real book is quoted; bet_link2 is only populated when a genuine second bookmaker exists. `value_indicator` (overpriced/underpriced/fair) is computed by comparing odds1 to sharpPrice. Same 12h staleness + 90min freshness filtering as arbitrage/middles. No the exchange filter applied. Cache 300s both tiers.

Query parameters
NameTypeRequiredDefaultDescription
sport_keystringNo–Filter by sport key. Lowercased.
sportstringNo–Filter by sport (also accepts sport_key fallback); uppercased.
minvaluenumberNo0Minimum EV value (also accepts min_value).
min_valuenumberNo0Alias for minvalue.
bookmakerstringNo–Substring match against either leg's bookmaker.
min_oddsnumberNo0Minimum odds1 threshold (also accepts minOdds).
minOddsnumberNo0Alias for min_odds.
limitnumberNo100Max rows per page. Clamped to 100 (free) / 10000 (api).
cursorstringNo–Opaque keyset-pagination cursor.
fieldsstring (comma-separated)No–Field projection list.
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/positive-ev?sport_key=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "snipe_5c02af",
      "event": "Boston Celtics vs Los Angeles Lakers",
      "home_team": "Boston Celtics",
      "away_team": "Los Angeles Lakers",
      "sport": "NBA",
      "sport_key": "basketball_nba",
      "market": "h2h",
      "selection1": "Boston Celtics",
      "bookmaker1": "sportsbet",
      "bookmaker2": "Betfair Fair",
      "odds1": 1.95,
      "odds2": null,
      "bet_link1": "https://www.sportsbet.com.au/...",
      "bet_link2": null,
      "line": null,
      "value": 5.6,
      "value_indicator": "underpriced",
      "sharp_price": 1.85,
      "confidence": "high",
      "instructions": "Back Boston Celtics @ sportsbet 1.95, sharp fair price implies 1.85.",
      "tool_type": "positive_ev",
      "status": "upcoming",
      "commence_time": "2026-09-03T23:10:00Z",
      "updated_at": "2026-09-03T09:58:00Z"
    }
  ],
  "meta": {
    "count": 1,
    "tier": "free",
    "limit": 100,
    "requested_limit": 100,
    "next_cursor": null,
    "filters": {
      "sport": null,
      "sport_key": "basketball_nba",
      "bookmaker": null,
      "min_value": null,
      "min_odds": null
    },
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 100,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}
GET/api/v1/opportunities/sgm-picksFree tier

AI-generated Same-Game-Multi (SGM) leg combinations with confidence tier, fair/minimum odds, and combined win probability.

Only opportunities route that reads via the secondary store (the secondary store) as its primary path, with an the primary-store flag feature-flagged the primary store attempt first that falls back to the secondary store on failure — returns 503 'Database unavailable' if the secondary store admin isn't initialized, even if the primary store would have worked (503 check happens before the primary store attempt). Only future events are returned (commenceTime >= now); no staleness/freshness filtering like the other opportunities routes since these are pre-game AI picks, not live odds edges. Cache 60s both tiers. No meta.limit/requested_limit/next_cursor fields in the response (unlike arbitrage/middles/etc). `legs[]` is passed through **verbatim from the suggestion document** (37 optional keys per leg as stored) — there is no per-leg `market`/`odds` field.

Query parameters
NameTypeRequiredDefaultDescription
sportstringNo–Filter by sport key (also accepts sport_key). Lowercased.
sport_keystringNo–Alias for sport.
tierenum(safe|value|longshot)No–Filter to a risk tier; any other value is ignored (no filter).
min_confidencenumberNo1Minimum confidence score, clamped to 1-5.
limitnumberNo20Max rows returned. Clamped to 50 (free) / 1000 (api).
Example request
curl \
  -H "X-API-Key: YOUR_KEY" \
  "https://krokodds.com.au/api/v1/opportunities/sgm-picks?sport=aussierules_afl"
Example response
{
  "success": true,
  "data": [
    {
      "id": "sgm_pick_a91cd0",
      "event_id": "basketball_nba_20260903_lal_bos",
      "event": "Boston Celtics vs Los Angeles Lakers",
      "sport": "NBA",
      "sport_key": "basketball_nba",
      "commence_time": "2026-09-03T23:10:00Z",
      "tier": "value",
      "confidence": 4,
      "confidence_label": "High",
      "legs": [
        {
          "marketKey": "alternate_totals",
          "marketLabel": "Alt Total",
          "selection": "Over +3.5",
          "line": 3.5,
          "openingLine": 3.5,
          "bestOdds": 1.15,
          "openingBestOdds": 1.12,
          "bestBookmaker": "Unibet",
          "bookCount": 4,
          "impliedProbability": 0.87,
          "marketImpliedProbability": 0.85,
          "evPercent": null,
          "hasEvEdge": false,
          "hitRate": null,
          "projection": null,
          "team": null,
          "playerName": null,
          "probSource": "implied",
          "signals": [
            {
              "type": "high_probability",
              "label": "85% fair implied",
              "strength": 3
            }
          ],
          "lockReason": null,
          "lineHistory": []
        }
      ],
      "fair_odds_conservative": 2.9,
      "minimum_acceptable_odds": 2.6,
      "combined_probability": 0.365,
      "suggested_bookmaker": "sportsbet",
      "bet_link": "https://www.sportsbet.com.au/...",
      "resolution": "pending"
    }
  ],
  "meta": {
    "count": 1,
    "tier": "free",
    "timestamp": "2026-09-03T10:06:00Z",
    "rate_limit": {
      "limit": 50,
      "remaining": 940,
      "reset": "2026-10-01T00:00:00Z"
    }
  }
}

Other categories

Gameday8 endpoints

Per-event head-to-head odds, alternate lines, best prices across books, player props, live state and AI-generated summaries.

Racing23 endpoints

The full Australian racing stack — thoroughbred, harness and greyhound. Meetings and runner fields, form, sectionals, ratings, market movers, exchange arbs, tote pools and results.

Odds Feed16 endpoints

Raw per-bookmaker prices as scraped, before any analysis layer. `/scraped-odds/*` paths are aliases of the equivalent `/odds-feed/*` route.

Sports8 endpoints

Sport reference data, team form guides, league standings, injury reports and weather-impact analysis.

Tips & Predictions4 endpoints

AI-generated betting tips with confidence scores and historical accuracy tracking.

Insights1 endpoint

Ranked leaderboards built on top of the stat feeds — per-sport player leaders for a chosen stat, season and view.

Tennis5 endpoints

ATP/WTA rankings, upcoming fixtures, match results, Elo ratings and surface-specific stats.

Historical7 endpoints

Deep archives — player and team game logs, shot charts, AFL/NRL historical data. Paid API plan only.

Reference7 endpoints

Static lookup data for building your own UI — players, venues, headshots, team logos and sport/league metadata.

Other Sports22 endpoints

Cricket, cycling, esports, Formula 1, golf, MMA, boxing, NFL and soccer coverage beyond the AU staples.

Infrastructure15 endpoints

Account and billing endpoints, bulk export, SSE streaming, webhook management and API health.

Additional Data19 endpoints

Odds history, steam-move detection, player prop stats/results, injuries, weather and other supporting feeds.

Prediction Markets3 endpoints

Polymarket and Kalshi sentiment plus a universal prediction-market catalogue and resolutions feed.